Derived classes
Cualquier tipo de clase (ya sea declarado con class-key class o struct ) puede declararse como derivada de una o más clases base que, a su vez, pueden derivar de sus propias clases base, formando una jerarquía de herencia.
Contenidos |
Sintaxis
La lista de clases base se proporciona 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 base
.
| attr (opcional) class-or-computed | (1) | ||||||||
attr
(opcional)
virtual
class-or-computed
|
(2) | ||||||||
| attr (opcional) access-specifier class-or-computed | (3) | ||||||||
attr
(opcional)
virtual
access-specifier
class-or-computed
|
(4) | ||||||||
attr
(opcional)
access-specifier
virtual
class-or-computed
|
(5) | ||||||||
virtual
y
access-specifier
pueden aparecer en cualquier orden.
| attr | - | (desde C++11) secuencia de cualquier número de atributos | ||||
| access-specifier | - | uno de private , public , o protected | ||||
| class-or-computed | - |
uno de
|
Un elaborated type specifier no puede aparecer directamente como class-or-computed debido a limitaciones de sintaxis.
|
base-specifier s en una base-clause pueden ser expansiones de paquete .
Una clase o struct declarada
|
(desde C++11) |
Si access-specifier se omite, por defecto será public para clases derivadas declaradas con class-key struct y será private para clases derivadas declaradas con class-key class .
struct Base { int a, b, c; }; // cada objeto de tipo Derived incluye Base como un subobjeto struct Derived : Base { int b; }; // cada objeto de tipo Derived2 incluye Derived y Base como subobjetos struct Derived2 : Derived { int c; };
Las clases denotadas por class-or-computed 's listadas en la base-clause son clases base directas. Sus bases son clases base indirectas. La misma clase no puede especificarse como clase base directa más de una vez, pero la misma clase puede ser tanto clase base directa como indirecta.
Cada clase base directa e indirecta está presente, como subobjeto de clase base , dentro de la representación del objeto de la clase derivada en un desplazamiento dependiente del ABI. Las clases base vacías normalmente no incrementan el tamaño del objeto derivado debido a la optimización de base vacía . Los constructores de los subobjetos de clase base son llamados por el constructor de la clase derivada: los argumentos pueden ser proporcionados a esos constructores en la lista de inicialización de miembros .
Clases base virtuales
Para cada clase base distinta que se especifique virtual , el objeto más derivado contiene solo un subobjeto de clase base de ese tipo, incluso si la clase aparece muchas veces en la jerarquía de herencia (siempre que se herede virtual cada vez).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // cada objeto de tipo AA tiene un X, un Y, un Z y dos B: // uno que es la base de Z y uno que es compartido por X e Y struct AA : X, Y, Z { AA() { X::n = 1; // modifica el miembro del subobjeto virtual B Y::n = 2; // modifica el mismo miembro del subobjeto virtual B Z::n = 3; // modifica el miembro del subobjeto no virtual B 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 iostreams de la biblioteca estándar: std::istream y std::ostream se derivan de std::ios usando herencia virtual. std::iostream se deriva tanto de std::istream como de std::ostream , por lo que cada instancia de std::iostream contiene un subobjeto std::ostream , un subobjeto std::istream , y solo un subobjeto std::ios (y, en consecuencia, un std::ios_base ).
Todos los subobjetos de base virtual se inicializan antes que cualquier subobjeto de base no virtual, por lo que solo la clase más derivada llama a los constructores de las bases virtuales en su lista de inicialización 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 está involucrada la herencia virtual (a veces referidas como las reglas de dominancia).
Herencia pública
Cuando una clase utiliza el public especificador de acceso de miembro para derivar de una base, todos los miembros públicos de la clase base son accesibles como miembros públicos de la clase derivada y todos los miembros protegidos de la clase base son accesibles como miembros protegidos de la clase derivada (los miembros privados de la base nunca son accesibles a menos que se establezca amistad).
La herencia pública modela la relación de subtipado de la programación orientada a objetos: el objeto de la clase derivada ES-UN objeto de la clase base. Se espera que las referencias y punteros a un objeto derivado sean utilizables por cualquier código que espere referencias o punteros a cualquiera de sus bases públicas (ver LSP ) o, en términos de DbC , una clase derivada debe mantener los invariantes de clase de sus bases públicas, no debe fortalecer ninguna precondición ni debilitar ninguna postcondición de una función miembro que override .
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu es un vector de MenuOption: las opciones pueden insertarse, eliminarse, reordenarse... // y tiene un título. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // Nota: Menu::title no es problemático porque su función es independiente de la clase base. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* específico del sistema operativo */ } // ¡ESTO ES INCORRECTO! // ColorMenu es un Menu donde cada opción tiene un color personalizado. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu necesita los siguientes invariantes que no pueden satisfacerse // heredando públicamente de Menu, por ejemplo: // - ColorMenu::colors y Menu deben tener el mismo número de elementos // - Para tener sentido, llamar a erase() debería eliminar también elementos de colors, // para permitir que las opciones mantengan sus colores // Básicamente cada llamada no constante a un método de std::vector romperá el invariante // del ColorMenu y necesitará corrección del usuario gestionando correctamente los colores. int main() { ColorMenu color_menu; // El gran problema de esta clase es que debemos mantener ColorMenu::Color // sincronizado con Menu. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // ¡ERROR! colors[i] en print() está fuera de rango color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colors y Menu tienen el mismo número de elementos }
Herencia protegida
Cuando una clase utiliza protected especificador de acceso de miembro para derivar de una base, todos los miembros públicos y protegidos de la clase base son accesibles como miembros protegidos de la clase derivada (los miembros privados de la base nunca son accesibles a menos que se establezca amistad).
La herencia protegida puede utilizarse para "polimorfismo controlado": dentro de los miembros de Derived, así como dentro de los miembros de todas las clases derivadas posteriores, la clase derivada ES-UN base: las referencias y punteros a Derived pueden usarse donde se esperan referencias y punteros a Base.
Herencia privada
Cuando una clase utiliza el private especificador de acceso de miembro para derivar de una base, todos los miembros públicos y protegidos de la clase base son accesibles como miembros privados de la clase derivada (los miembros privados de la base nunca son accesibles a menos que se establezca amistad).
La herencia privada se utiliza comúnmente en el diseño basado en políticas, ya que las políticas suelen ser clases vacías, y utilizarlas como bases permite tanto el polimorfismo estático como el aprovechamiento de la optimización de base vacía .
La herencia privada también puede utilizarse para implementar la relación de composición (el subobjeto de la clase base es un detalle de implementación del objeto de la clase derivada). El uso de un miembro ofrece mejor encapsulación y generalmente es preferido a menos que la clase derivada requiera acceso a miembros protegidos (incluyendo constructores) de la base, necesite anular un miembro virtual de la base, necesite que la base se construya antes y se destruya después de algún otro subobjeto base, necesite compartir una base virtual o necesite controlar la construcción de una base virtual. El uso de miembros para implementar composición tampoco es aplicable en el caso de herencia múltiple desde un parameter pack o cuando las identidades de las clases base se determinan en tiempo de compilación mediante metaprogramación de plantillas.
Similar a la herencia protegida, la herencia privada también puede utilizarse para polimorfismo controlado: dentro de los miembros de la clase derivada (pero no dentro de clases más derivadas), la derivada ES-UN base.
template<typename Transport> class service : private Transport // herencia privada de la política de Transporte { public: void transmit() { this->send(...); // enviar usando cualquier transporte proporcionado } }; // política de transporte TCP class tcp { public: void send(...); }; // política de transporte UDP class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // enviar a través de TCP
Búsqueda de nombre de miembro
Las reglas de búsqueda de nombres calificados y no calificados para miembros de clase se detallan en name lookup .
Palabras clave
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1710 | C++98 |
la sintaxis de
class-or-decltype
imposibilitaba derivar de
una clase dependiente donde se requiere el template desambiguador |
se permite template |