que programacion polimorfismo orientada objetos objeto herencia ejemplo destructores constructores codigo clases c++ inheritance constructor destructor

programacion - C++ herencia Constructor/Destructor



programacion orientada a objetos (6)

EDITAR: Resumen de respuestas

A continuación, B es una subclase de A.

Es una cuestión de terminología; Los ctors y dtors no son heredados, en el sentido de que el ctor / dtor de B no se tomará prestado de la interfaz de A. Una clase tiene al menos un constructor y tiene exactamente un destructor.

  • Constructores :
    • B no hereda constructores de A;
    • A menos que el ctor de B explícitamente llame a uno de los ctores de A, se llamará automáticamente al ctor predeterminado de A antes del cuerpo ctor de B (la idea es que A debe inicializarse antes de que B se cree).
  • Destructores :
    • B no hereda el dtor de A;
    • Después de que sale, el destructor de B llamará automáticamente al destructor de A.

Agradecimientos: me gustaría agradecer especialmente a Oli Charlesworth y a Kos por sus respuestas. Configuré la respuesta de Kos como la solución porque era la que mejor entendía.

POSTE ORIGINAL

Cuando busca "Sitio de herencia de destructor de C ++: stackoverflow.com" en Google, actualmente encuentra las siguientes publicaciones:

  1. Constructor y herencia del destructor : dos usuarios con una reputación de más de 30.000 dicen que se hereda y que no es
  2. ¿Se heredan los destructores virtuales? : aquí no se menciona nada que apunte a que los destructores no son heredados
  3. Destructores y herencia en C ++? : Los comentarios parecen indicar que los destructores son heredados

P1: Lo que también sé por la práctica, es que no se puede inicializar un objeto derivado con el mismo prototipo que su constructor padre sin definir explícitamente un constructor para la clase derivada, ¿es correcto?

A pesar de que es bastante claro por las publicaciones que los destructores parecen heredarse, todavía estoy desconcertado por el hecho de que un usuario con una reputación de 32k diría que no. Escribí un pequeño ejemplo que debería aclarar la mente de todos:

#include <cstdio> /******************************/ // Base class struct A { A() { printf( "/tInstance counter = %d (ctor)/n", ++instance_counter ); } ~A() { printf( "/tInstance counter = %d (dtor)/n", --instance_counter ); } static int instance_counter; }; // Inherited class with default ctor/dtor class B : public A {}; // Inherited class with defined ctor/dtor struct C : public A { C() { printf("/tC says hi!/n"); } ~C() { printf("/tC says bye!/n"); } }; /******************************/ // Initialize counter int A::instance_counter = 0; /******************************/ // A few tests int main() { printf("Create A/n"); A a; printf("Delete A/n"); a.~A(); printf("Create B/n"); B b; printf("Delete B/n"); b.~B(); printf("Create new B stored as A*/n"); A *a_ptr = new B(); printf("Delete previous pointer/n"); delete a_ptr; printf("Create C/n"); C c; printf("Delete C/n"); c.~C(); }

y aquí está el resultado (compilado con g ++ 4.4.3):

Create A Instance counter = 1 (ctor) Delete A Instance counter = 0 (dtor) Create B Instance counter = 1 (ctor) Delete B Instance counter = 0 (dtor) Create new B stored as A* Instance counter = 1 (ctor) Delete previous pointer Instance counter = 0 (dtor) Create C Instance counter = 1 (ctor) C says hi! Delete C C says bye! Instance counter = 0 (dtor) // We exit main() now C says bye! Instance counter = -1 (dtor) Instance counter = -2 (dtor) Instance counter = -3 (dtor)

P2: ¿Alguien que piense que no es heredado, por favor explique eso?

P3: Entonces, ¿qué sucede cuando llamas al constructor de una subclase con entradas? ¿También se llama al "constructor vacío" de la superclase?


P1: Lo que también sé por la práctica, es que no se puede inicializar un objeto derivado con el mismo prototipo que su constructor padre sin definir explícitamente un constructor para la clase derivada, ¿es correcto?

Aparte del caso trivial en el que ha definido un constructor predeterminado en la superclase, sí, está en lo cierto.

P2: ¿Alguien que piense que no es heredado, por favor explique eso?

Esto puede ser una cuestión de definiciones de terminología. Si bien está claro que los destructores virtuales existen y funcionan "como se esperaba", vemos en el estándar C ++ ([class.virtual]):

Aunque los destructores no se heredan , un destructor en una clase derivada anula un destructor de clase base declarado virtual

(énfasis mío)

P3: Entonces, ¿qué sucede cuando llamas al constructor de una subclase con entradas? ¿También se llama al "constructor vacío" de la superclase?

Si no invocas explícitamente un constructor de superclase específico, se llamará al constructor de superclase predeterminado (suponiendo que esté visible).


En su ejemplo, llama explícitamente a las funciones de destructor. Esto es legal (obviamente, ya que se compiló y ejecutó) pero casi siempre es incorrecto.

Para los objetos asignados dinámicamente creados con new , el destructor se ejecutará cuando se elimine el objetado con delete .

Para los objetos asignados estáticamente, que se crean simplemente declarando el objeto dentro del alcance de una función, el destructor se ejecuta cuando el alcance del objeto desaparece. Es decir, cuando sale main() , se ejecutarán los destructores de los objetos. ¡Pero ya ha ejecutado los destructores de esos objetos llamándolos manualmente! Esta es la razón por la que la salida de su ejemplo muestra que el conteo disminuye a -3 ... ha ejecutado los destructores para a , b y c dos veces.

Este es el mismo código, anotado para mostrar cuando los destructores se ejecutarán automáticamente:

int main() { printf("Create A/n"); A a; printf("Delete A/n"); a.~A(); printf("Create B/n"); B b; printf("Delete B/n"); b.~B(); printf("Create new B stored as A*/n"); A *a_ptr = new B(); printf("Delete previous pointer/n"); delete a_ptr; // Implicitly calls destructor for a_ptr. a_ptr is class B, // so it would call a_ptr->~B() if it existed. Because B is an A, after // its destructor is called, it calls the superclass''s destructor, // a_ptr->~A(). printf("Create C/n"); C c; printf("Delete C/n"); c.~C(); } // Function exits here at the close brace, so anything declared in its scope is // deallocated from the stack and their destructors run. // First `c` is destroyed, which calls c.~C(), then because C is a subclass of A // calls c.~B() (which doesn''t exist, so a blank implementation is used), then // because B is a subclass of A calls c.~A(). This decrements the counter, but // the count is wrong because you already manually called c.~C(), which you // ordinarily shouldn''t have done. // Then `b` is destroyed, in a similar manner. Now the count is off by 2, // because you had already called b.~B(). // Lastly `a` is destroyed, just as above. And again, because you had already // called a.~A(), the count is now off by 3.


Los destructores no son heredados. Si una clase no define uno, el compilador genera uno. Para casos triviales, ese destructor solo llama al destructor de la clase base, y a menudo eso significa que no hay un código explícito para su destructor (que imita la herencia). Pero si una clase tiene miembros con destructores, el destructor generado llama a los destructores para esos miembros antes de llamar al destructor de la clase base. Eso es algo que una función heredada no haría.


Técnicamente, los destructores SON heredados. Pero en circunstancias normales, los destructores heredados no se usan directamente para una clase derivada; se invocan porque el propio destructor de la clase derivada los llama para destruir sus propios "subobjetos de clase base" como un paso dentro de la destrucción del objeto más grande. Y en las circunstancias inusuales en las que utiliza directamente un destructor de clase base en un objeto derivado, es muy difícil evitar el Comportamiento no definido.

Este ejemplo viene directamente del Estándar C ++ (12.4p12).

struct B { virtual ~B() { } }; struct D : B { ~D() { } }; D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B''s destructor B_ptr->~B(); // calls D''s destructor B_ptr->~B_alias(); // calls D''s destructor B_ptr->B_alias::~B(); // calls B''s destructor B_ptr->B_alias::~B_alias(); // calls B''s destructor }

Si ~B no fuera un miembro heredado de D , la primera afirmación en f estaría mal formada. Tal como es, es C ++ legal, aunque extremadamente peligroso.


Terminología, terminología ...

OK, ¿qué queremos decir con "Foo is inherited"? Queremos decir que si los objetos de la clase A tienen Foo en su interfaz, entonces los objetos de la clase B que es una subclase de A también tienen Foo en su interfaz.

  • Los constructores no son parte de la interfaz de los objetos. Pertenecen directamente a las clases. Las clases A y B pueden proporcionar conjuntos de constructores completamente diferentes. No "ser heredado" aquí.

    ( Detalle de implementación: los constructores de cada B llaman a un constructor de A ) .

  • Los destructores son, de hecho, una parte de la interfaz de cada objeto, ya que el usuario del objeto es responsable de llamarlos (es decir, directamente con la delete o indirectamente al dejar un objeto fuera del alcance). Cada objeto tiene exactamente un destructor : su propio destructor, que podría ser opcionalmente uno virtual. Siempre es propio, y no es heredado.

    (Detalle de implementación: el destructor de B llama al destructor de A).

Entonces, hay una conexión entre constructores y destructores base y derivados, pero no es como "se heredan".

Espero que esto responda a lo que tienes en mente.


La herencia es qué: mecanismo de reutilización y ampliación de las clases existentes sin modificarlas, lo que produce relaciones jerárquicas entre ellas.

La herencia es casi como insertar un objeto en una clase.

cuando la clase está heredando una clase base, el constructor de la clase base se llama primero y luego la clase derivada, y la llamada del destructor está en orden inverso.

Entonces, ¿por qué se llama a Base Class Constructor? (Llamado no heredado puede estar con parámetros / predeterminado): para garantizar que la clase base está construida correctamente cuando se ejecuta el constructor para la clase derivada.

Ahora Calling of Destructor (llamando no heredar): cuando el objeto base se sale del alcance, se llama al destructor por su cuenta. Así que hay un problema np de la herencia del destructor.

ahora tus preguntas:

ans 1 - sí, estás en lo correcto para la primera pregunta.
ans 2 - entonces destructor se llama no heredado después de que el alcance del objeto se apaga.
& ans 3 - si en la clase derivada estás dando la llamada con parámetros, entonces solo se llamará a ese constructor, con él no se llamaría a ningún otro constructor.
no tiene sentido que se llame a 2 constructores del mismo objeto sobre la creación de objetos, como el constructor llamó a la creación de un objeto. Prepara el nuevo objeto para su uso. Por lo tanto, no hay ninguna lógica para preparar el objeto dos veces con diferentes constructores.