¿Un objeto de clase derivado contiene un objeto de clase base?

Considere el siguiente código de ejemplo a continuación:

#include  using namespace std; class base { public: base() { cout << "ctor in base class\n"; } }; class derived1 : public base { public: derived1() { cout <<"ctor in derived class\n"; } }; int main() { derived1 d1obj; return 0; } 

Preguntas

  1. Cuando se crea d1obj , los constructores se invocan en el orden de derivación: el constructor de la clase base se llama primero y luego el constructor de la clase derivada. ¿Se hace esto debido a la siguiente razón: In-order to construct the derived class object the base class object needs to be constructed first ?

  2. ¿ d1obj contiene un objeto de clase base?

Estoy agregando una pregunta más

3) Cuando se crea d1obj, el control primero llega al constructor de la clase base y luego va al constructor de la clase derivada. ¿O es al revés: primero llega al constructor de la clase derivada, encuentra que tiene una clase base y, por lo tanto, el control va al constructor en la clase base?

1) Sí, las bases se construyen primero, luego los miembros de datos no estáticos, luego se llama al constructor de la clase derivada. La razón es para que el código en el constructor de esta clase pueda ver y usar una base completamente construida.

2) si. Puede tomar esto completamente literalmente: dentro de la memoria asignada al objeto de clase derivado, hay una región llamada “subobjeto de clase base”. Un objeto de clase derivado “contiene” un subobjeto de clase base exactamente de la misma manera que contiene subobjetos de miembro para cualquier miembro de datos no estáticos. Sin embargo, en realidad, el ejemplo dado en la pregunta es un caso especial: la “optimización de clase base vacía”. Se permite que este subobjeto de clase base sea de tamaño cero, incluso aunque los objetos completos de tipo base nunca sean de tamaño cero.

Sin embargo, esta contención es una cosa de bajo nivel. Es cierto que otros dicen que las bases conceptuales son diferentes de los miembros, y la syntax y la semántica del lenguaje los tratan de manera diferente, aunque los subobjetos en sí mismos son solo partes del diseño de la clase.

3) Este es un detalle de implementación. El código en el cuerpo del constructor de la clase base se ejecuta antes que el código en el cuerpo del constructor de la clase derivada y, en efecto, el constructor de la clase derivada se ejecuta en un bloque try / catch invisible generado por el comstackdor para asegurar que si se lanza , la clase base es destruida. Pero depende del comstackdor cómo lograr esto en términos de lo que realmente hacen los puntos de entrada de función en el código emitido.

Cuando una clase tiene bases virtuales, es común que un constructor produzca dos cuerpos de función diferentes, uno para usar cuando esta clase es el tipo más derivado y uno para usar cuando esta clase es en sí misma una clase base. La razón es que las clases de base virtual son construidas por la clase más derivada, para garantizar que cuando se comparten solo se crean una vez. Así que la primera versión del constructor llamará a todos los constructores de clase base, mientras que la segunda versión solo llamará a los constructores para bases no virtuales.

El comstackdor siempre “sabe” qué bases tiene la clase, porque solo puede construir un objeto de un tipo completo, lo que implica que el comstackdor puede ver la definición de la clase, y eso especifica las bases. Por lo tanto, no hay duda de “encontrar que tiene una clase base” cuando se ingresa al constructor; el comstackdor sabe que tiene una clase base, y si la llamada al constructor de la clase base está ubicada dentro del código del constructor de la clase derivada, eso es Sólo para la comodidad del comstackdor. Podría emitir las llamadas a los constructores de la clase base en cada lugar donde construya un objeto, y en este caso, en los casos en que el constructor de la clase derivada puede estar y está en línea, ese es el efecto final.

  1. Sí. Imagine que en el constructor de la clase derivada desea utilizar algunos miembros de la clase base. Por lo tanto, necesitan ser inicializados. Por lo tanto, tiene sentido que el constructor de la clase base se llame primero.

  2. d1obj es un objeto de clase base. Eso es lo que es la herencia. De alguna manera se podría decir que contiene un objeto de la clase base. En la memoria, la primera parte del objeto se corresponderá con un objeto base (en su ejemplo, no tiene funciones virtuales; si las tuviera, tendría un puntero a la vftable de derived1 primero, luego a los miembros de su clase base) y después que los miembros pertenecientes a derived1 .

Si y si.


Desde su edición, anuncio 3) El constructor de la clase derivada llama al constructor de la clase base como su primer acto de servicio; Luego todos los constructores de objetos miembros, y finalmente ejecuta el cuerpo del constructor.

La destrucción funciona de manera opuesta: primero se ejecuta el cuerpo del destructor, luego se destruyen los objetos miembros (en orden inverso a su destrucción) y, finalmente, se llama al destructor de subobjetos base (por lo que siempre se necesita un destructor accesible en la clase base, incluso si es puro-virtual).

1– Sí. Y así es lógico.

Los objetos de tipo derivado1 son objetos especiales de tipo base, lo que significa que, como primera cosa, son objetos de tipo base. Esto es lo que se construye primero, luego “derivado1” agrega su “especialidad” al objeto.

2– No se trata de contener, es herencia. Vea mi párrafo anterior para entender mejor esta respuesta.

  1. Bueno, conceptualmente, en realidad no. d1obj contiene todos los miembros de datos que una instancia de base y responde a todas las funciones miembros que dicha instancia, pero no “contiene” una instancia de base : no puede decir d1obj.base.func() por ejemplo . Si, por ejemplo, ha sobrecargado un método declarado por su padre, puede, sin embargo, llamar a d1obj.base::func() para obtener su implementación, en lugar de solo llamar a d1obj->func() .

Si bien puede parecer un poco como dividir los pelos para decir que contiene todos los datos y métodos de su padre sin que contenga conceptualmente una instancia de su padre, esta es una distinción importante, ya que a menudo puede obtener muchos de los beneficios de la herencia directa. creando una clase que contenga la clase “padre” como datos de miembros, así:

 class derived2 /*no parent listed */ { public: derived2() :_b() {} private: base _b; } 

Tal construcción le permite hacer uso de métodos ya implementados por base , como detalles de implementación de sus propios métodos, sin tener que exponer ningún método al que se haya declarado público pero al que no quiera otorgar acceso. Un ejemplo importante de esto es la stack , que puede contener una instancia de otro contenedor STL (como vector , deque o list ), luego usar su back() para implementar top() , push_back() para push() y pop_back() para pop() , todo sin tener que exponer los métodos originales al usuario.