subprogramas - herencia simple y herencia multiple c++
¿Por qué se llama al destructor de clase base(virtual) cuando se elimina un objeto de clase derivado? (7)
Una diferencia entre un destructor (por supuesto, también el constructor) y otras funciones miembro es que, si una función miembro normal tiene un cuerpo en la clase derivada, solo se ejecuta la versión de la clase Derivada. ¿Mientras que en el caso de los destructores, tanto las versiones derivadas como las de clase base se ejecutan?
Será genial saber qué sucede exactamente en el caso de destructor (quizás virtual) y constructor, que se llaman para todas sus clases base, incluso si se elimina el objeto de clase más derivado.
¡Gracias por adelantado!
Como dice Igor, los constructores deben ser llamados para las clases base. Considere lo que pasaría si no se llamara:
struct A {
std::string s;
virtual ~A() {}
};
struct B : A {};
Si no se llamara al destructor para A
al eliminar una instancia de B
, A
nunca se limpiaría.
Cuando se destruye cualquier objeto, los destructores se ejecutan para todos los subobjetos. Esto incluye tanto la reutilización por contención como la reutilización por herencia.
Esto es por diseño. Se debe llamar al destructor en la clase base para que libere sus recursos. La regla de oro es que una clase derivada solo debe limpiar sus propios recursos y dejar que la clase base se limpie a sí misma.
De la especificación de C ++ :
Después de ejecutar el cuerpo del destructor y destruir cualquier objeto automático asignado dentro del cuerpo, un destructor para la clase X llama a los destructores para los miembros directos de X, los destructores para las clases básicas directas de X y, si X es el tipo de la clase más derivada ( 12.6.2), su destructor llama a los destructores para las clases base virtuales de X. Se llama a todos los destructores como si estuvieran referenciados con un nombre calificado, es decir, ignorando cualquier posible destructor de reemplazo virtual en más clases derivadas. Las bases y los miembros se destruyen en el orden inverso a la finalización de su constructor (ver 12.6.2).
Además, dado que solo hay un destructor, no hay ambigüedad en cuanto a qué destructor debe llamar una clase. Este no es el caso de los constructores, donde un programador debe elegir a qué clase base se debe llamar si no hay un constructor predeterminado accesible.
La norma dice
Después de ejecutar el cuerpo del destructor y destruir cualquier objeto automático asignado dentro del cuerpo, un destructor para la clase X llama a los destructores para los miembros directos sin variante de X , los destructores para las clases básicas directas de X y, si X es el tipo de más clase derivada (12.6.2), su destructor llama a los destructores para las clases base virtuales de X. Se llama a todos los destructores como si estuvieran referenciados con un nombre cali fi cado, es decir, ignorando cualquier posible destructor de reemplazo virtual en más clases derivadas. Las bases y los miembros se destruyen en el orden inverso a la finalización de su constructor (ver 12.6.2). Una declaración de retorno (6.6.3) en un destructor podría no regresar directamente al llamante; Antes de transferir el control al llamante, se llaman los destructores para los miembros y las bases. Los destructores de los elementos de una matriz se llaman en orden inverso a su construcción (ver 12.6).
Asimismo, según los recursos RAII , deben estar vinculados a la vida útil de los objetos adecuados y se debe solicitar a los destructores de las clases respectivas que liberen los recursos.
Por ejemplo el siguiente código pierde memoria.
struct Base
{
int *p;
Base():p(new int){}
~Base(){ delete p; } //has to be virtual
};
struct Derived :Base
{
int *d;
Derived():Base(),d(new int){}
~Derived(){delete d;}
};
int main()
{
Base *base=new Derived();
//do something
delete base; //Oops!! ~Base() gets called(=>Memory Leak).
}
Porque así es como trabaja Dtor. Cuando creas un objeto, los ctors se invocan a partir de la base y van hasta lo más derivado. Cuando destruyes objetos (correctamente) sucede lo contrario. El momento en que hacer que un dtor virtual haga una diferencia es si / cuando destruye un objeto a través de un puntero (o referencia, aunque eso es bastante inusual) al tipo base. En ese caso, la alternativa no es realmente que solo se invoque al dtor derivado, sino que la alternativa es simplemente un comportamiento indefinido. Eso hace que se adopte la forma de invocar solo al dtor derivado, pero también puede tomar una forma completamente diferente.
Un destructor de clase base puede ser responsable de limpiar los recursos asignados por el constructor de la clase base.
Si su clase base tiene un constructor predeterminado (uno que no toma parámetros o tiene valores predeterminados para todos sus parámetros), al constructor se le llama automáticamente al construir una instancia derivada.
Si su clase base tiene un constructor que requiere parámetros, debe llamarlo manualmente en la lista de inicializadores del constructor de clase derivado.
A su destructor de clase base siempre se le llamará automáticamente al eliminar la instancia derivada ya que los destructores no toman parámetros.
Si está utilizando polimorfismo y su instancia derivada es apuntada por un puntero de clase base, entonces el destructor de clase derivada solo se llama si el destructor base es virtual.
Constructor y destructor son diferentes del resto de métodos regulares.
Constructor
- no puede ser virtual
- en la clase derivada se llama explícitamente constructor de la clase base
- o, en caso de que no llame, el compilador del constructor de la clase base insertará la llamada. Llamará al constructor base sin parámetros. Si no existe tal constructor, obtienes un error de compilación.
struct A {};
struct B : A { B() : A() {} };
// but this works as well because compiler inserts call to A():
struct B : A { B() {} };
// however this does not compile:
struct A { A(int x) {} };
struct B : A { B() {} };
// you need:
struct B : A { B() : A(4) {} };
Destructor
- Cuando llama al destructor en la clase derivada sobre un puntero o una referencia, donde la clase base tiene un destructor virtual, el destructor más derivado se llamará primero y luego el resto de las clases derivadas en orden inverso de construcción. Esto es para asegurarse de que toda la memoria se ha limpiado correctamente. No funcionaría si la clase más derivada se llamara en último lugar porque para ese momento la clase base no existiría en la memoria y obtendría segfault.
struct C
{
virtual ~C() { cout << __FUNCTION__ << endl; }
};
struct D : C
{
virtual ~D() { cout << __FUNCTION__ << endl; }
};
struct E : D
{
virtual ~E() { cout << __FUNCTION__ << endl; }
};
int main()
{
C * o = new E();
delete o;
}
salida:
~E
~D
~C
Si el método en la clase base está marcado como virtual
todos los métodos heredados también son virtuales, por lo que incluso si no marca los destructores en D
y E
como virtual
, seguirán siendo virtual
y seguirán siendo llamados en el mismo orden.