Clases derivadas
Cualquier tipo de clase (ya sea declarado con la clave-de-clase class
o struct
) puede declararse como derivada de una o más clases base que, a su vez, pueden derivarse de sus propias clases base, formando una jerarquía de herencia.
La lista de clases base se provee en la cláusula-base de la sintaxis de declaración de clase. La cláusula-base consiste en el carácter :
seguido de una lista separada por comas de uno o más especificadores-de-base.
atrib(opcional) especificador-de-acceso(opcional) especificador-virtual(opcional) clase-o-decltype | |||||||||
atrib | - | (desde C++11) Secuencia opcional de cualquier número de atributos. | ||
especificador-de-acceso | - | Uno de private , public , o protected .
| ||
especificador-virtual | - | La palabra clave virtual .
| ||
clase-o-decltype | - | uno de
|
El especificador-virtual y el especificador de acceso pueden aparecer en cualquier orden. Un especificador de tipo elaborado no puede aparecer directamente como clase-o-decltype debido a limitaciones de sintaxis.
El especificador-base o especificadores base en una cláusula-base puede o pueden ser una expansión de paquete o paquetes. Una clase o estructura declarada como final no puede aparecer en clase-o-decltype. |
(desde C++11) |
Si se omite el especificador de acceso, por defecto se establece como public
para clases declaradas con la clave-de-clase struct
, y como private
para clases declaradas con la clave-de-clase class
.
struct Base { int a, b, c; }; // todo objeto de tipo Derivada incluye a Base como un subobjeto struct Derivada : Base { int b; }; // todo objeto de tipo Derivada2 incluye a Derivada y a Base como subobjetos struct Derivada2 : Derivada { int c; };
Las clases indicadas por clase-o-decltype listadas en la cláusula-base son clases base directas. Sus bases son clases base indirectas. La misma clase no puede especificarse como una clase base directa más de una vez, pero la misma clase puede ser tanto una clase base directa como una clase base indirecta.
Cada clase base directa e indirecta se encuentra presente como un subobjeto de clase base, dentro de la representación de la clase derivada en un desplazamiento definido por la implementación. Las clases base vacías habitualmente no aumentan el tamaño del objeto derivado debido a la optimización de clase base vacía. Los constructores de los objetos de las clases base se llaman por el constructor de la clase derivada: los argumentos pueden proveerse a esos constructores en la lista inicializadora de miembros.
Contenido |
[editar] Clases base virtuales
Para cada clase base distinta que se especifica como virtual
, el objeto al final de la jerarquía contiene solamente un subobjeto de la clase base de ese tipo, aún si la clase aparece varias veces en la jerarquía de herencia (siempre y cuando se herede como virtual
en todas las ocasiones).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // todos los objetos de tipo AA tienen una X, una Y, una Z, y dos B: // una que es la base de Z y una que se comparte por X e Y struct AA : X, Y, Z { AA() { X::n = 1; // modifica el miembro del subobjeto de la B virtual Y::n = 2; // modifica el mismo miembro del subobjeto de la B virtual Z::n = 3; // modifica el miembro del subobjeto de la B no-virtual std::cout << X::n << Y::n << Z::n << '\n'; // imprime 223 } };
Un ejemplo de una jerarquía de herencia con clases base virtuales es la jerarquía de flujos de entrada/salida (iostreams) de la biblioteca estándar: std::istream y std::ostream se derivan de std::ios usando herencia virtual. std::iostream se deriva de ambas, std::istream y std::ostream, de tal manera que cada objeto creado de std::iostream contiene un subobjeto de tipo std::ostream, un subobjeto de tipo std::istream, y sólo un subobjeto de tipo std::ios (y consecuentemente, uno de tipo std::ios_base).
Todos los subobjetos base virtuales se inicializan antes que cualquier subobjeto base no virtual, así que solamente la clase al final de la jerarquía llama a los constructores de las bases virtuales en su lista inicializadora de miembros:
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // el constructor por defecto de AA llama a los constructores por defecto de X e Y // pero esos constructores no llaman al constructor de B porque B es una base virtual AA a; // a.n == 3 // el constructor por defecto de X llama al constructor de B X x; // x.n == 1
Existen reglas especiales para la búsqueda de nombres no calificados para miembros de clase cuando la herencia virtual está involucrada (se les conoce a veces como reglas de dominio).
[editar] Herencia pública
Cuando una clase utiliza el especificador de acceso a miembro public
para derivar de una base, todos los miembro públicos de la clase base pueden accederse como miembros públicos de la clase derivada, y todos los miembros protegidos de la clase base pueden accederse como miembros protegidos de la clase derivada. Los miembros privados de la clase base nunca pueden accederse, a menos que se suministre su acceso mediante el uso de friend
.
La herencia pública modela la relación de subtipo de la programación orientada a objetos: el objeto de la clase deriva ES-UN objeto de la clase base. Se espera que los punteros y referencias a un objeto derivado puedan usarse por cualquier código que espere referencias o punteros a cualquiera de sus clases base públicas (véase Principio de sustitución de Liskov) o, en términos de Diseño por contrato, una clase derivada deberá mantener invariables de clase de sus clases base públicas, no deberá fortalecer ninguna precondición o debilitar ninguna postcondición de una función miembro que invalide con el uso del especificador override.
#include <vector> #include <string> #include <iostream> struct OpcionMenu { std::string titulo; }; // Menu es un vector de OpcionMenu: la opciones se pueden insertar, eliminar, ordenar... // y tienen un título. class Menu : public std::vector<OpcionMenu> { public: std::string titulo; void print() const { std::cout << titulo << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i+1) << ". " << at(i).titulo << '\n'; } }; // Nota: Menu::titulo no es problemático puesto que su rol es independiente de la clase base. enum class Color { BLANCO, ROJO, AZUL, VERDE }; void aplicar_color_terminal(Color) { /* específico de S.O. */ } // ESTO ESTÁ MAL! // MenuColor es un Menu donde cada opción tiene un color propio. class MenuColor : public Menu { public: std::vector<Color> colores; void imprimir() const { std::cout << titulo << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i+1) << ". "; aplicar_color_terminal(colores[i]); std::cout << at(i).titulo << '\n'; aplicar_color_terminal(Color::BLANCO); } } }; // MenuColor necesita los siguiente invariantes que no se satisfacen // por la herencia pública de Menu, por ejemplo: // - MenuColor::colores y Menu tienen que tener el mismo número de elementos // - Para que tenga sentido, llamar a erase() también debería eliminar elementos de los colores, // para que las opciones mantengan sus colores // Basicamente toda llamada no constante a un método de std::vector romperá el invariante // de MenuColor y necesitará ser corregido por el usuario mediante la gestión correcta de los colores. int main() { MenuColor menu_color; // El gran problema de esta clase es que debemos mantener MenuColor::Color // sincronizado con Menu. menu_color.push_back(OpcionMenu{"Alguna selección"}); // menu_color.imprimir(); // ERROR! colores[i] en imprimir está fuera de rango menu_color.colores.push_back(Color::ROJO); menu_color.imprimir(); //Correcto: colores y Menu tienen el mismo número de elementos }
[editar] Herencia protegida
Cuando una clase utiliza el especificador de acceso a miembro protected
para derivar de una base, todos los miembros públicos y protegidos de la clase base pueden accederse como miembros protegidos de la clase derivada (los miembros privados de la base nunca pueden accederse a menos que exista algún tipo de amistad entre las dos clases mediante el especificador friend
.
La herencia protegida puede usarse para "polimorfismo controlado": dentro de los miembros de la clase derivada, así como dentro de los miembros de todas las clases derivadas posteriormente, la clase derivada ES-UNA base: referencias y punteros a la clase derivada pueden usarse donde se esperen referencias y punteros a la clase base.
[editar] Herencia privada
Cuando una clase utiliza el especificador de acceso a miembro private
para derivar de una base, todos los miembros públicos y protegidos de la clase base pueden accederse como miembros privados de la clase derivada (los miembros privados de la base nunca pueden accederse a menos que exista algún tipo de amistad entre las dos clases mediante el especificador friend
.
La herencia privada se usa comúnmente en diseños basados en política, ya que las políticas usualmente son clases vacías, y usarlas como bases habilita el polimorfismo estático y explota la optimización de clase base vacía
La herencia privada también puede usarse para implementar la relación de composición (el subobjeto de la clase base es un detalle de la implementación del objeto de la clase derivada). Usar un miembro ofrece mejor encapsulación y generalmente se prefiere, a menos que la clase derivada requiera acceso a miembros protegidos (incluyendo constructores) de la base, necesite redefinir (override) un miembro virtual de la base, necesite que la base se construya antes y se destruya después de algún otro subobjeto de (otra) base, necesite compartir una base virtual o necesite controlar la construcción de una base virtual.
El uso de miembros para implementar la composición no se aplica en el caso de herencia múltiple de un paquete de parámetros o cuando las identidades de las clases base se determinan en tiempo de compilación mediante la metaprogramación de plantillas.
De manera similar a la herencia protegida, la herencia privada también puede ser usada para polimorfismo controlado: dentro de los miembros de la clase derivada (pero no dentro de los miembros de clase derivadas posteriormente), la clase derivada ES-UNA base.
template<typename Transporte> class servicio : private Transporte // herencia privada de la política Transporte { public: void transmitir() { this->enviar(...); // enviar usando el transporte suministrado } }; // política de transporte TCP class tcp { public: void enviar(...); }; // política de transporte UDP class udp { public: void enviar(...); }; servicio<tcp> servicio(host, port); servicio.transmitir(...); // enviar sobre TCP
[editar] Búsqueda de nombres de miembros
Las reglas para la búsqueda no calificada y calificada de los miembros de clase se detallan en búsqueda de nombres.