Entiendo que con herencia pública en general es no seguro, ya que cuando delete
en un puntero de clase base el compilador genera código para llamar al destructor de la clase base y no se llama a la clase derivada.¿Es seguro heredar de forma privada de una clase con un destructor no virtual?
Sin embargo, para la herencia privada el cliente no puede convertir un puntero clase derivada a un puntero de clase base (como la herencia privada no modela es, una relación), por lo delete
siempre se usa en el puntero de la clase derivada, y el compilador debería ser capaz de ver que también hay una clase base y llamar a su destructor.
Hice esta prueba:
#include <iostream>
struct BaseVirtual
{
virtual ~BaseVirtual()
{
std::cout << "BaseVirtual's dtor" << '\n';
}
};
struct BaseNonVirtual
{
~BaseNonVirtual()
{
std::cout << "BaseNonVirtual's dtor" << '\n';
}
};
struct DerivedPrivVirtual: private BaseVirtual
{
static void f()
{
BaseVirtual * p = new DerivedPrivVirtual;
delete p;
}
~DerivedPrivVirtual()
{
std::cout << "DerivedPrivVirtual's dtor" << '\n';
}
};
struct DerivedPrivNonVirtual: private BaseNonVirtual
{
static void f()
{
BaseNonVirtual * p = new DerivedPrivNonVirtual;
delete p;
}
~DerivedPrivNonVirtual()
{
std::cout << "DerivedPrivNonVirtual's dtor" << '\n';
}
};
int main()
{
std::cout << "With explicit derived pointer type:" << '\n';
{
DerivedPrivVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
DerivedPrivNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;
delete derivedPrivVirtual;
delete derivedPrivNonVirtual;
}
std::cout << '\n';
std::cout << "With base pointer type:" << '\n';
{
// Client code can't cast Derived to Base when inherit privately.
//BaseVirtual * derivedPrivVirtual = new DerivedPrivVirtual;
//BaseNonVirtual * derivedPrivNonVirtual = new DerivedPrivNonVirtual;
//delete derivedPrivVirtual;
//delete derivedPrivNonVirtual;
}
std::cout << '\n';
std::cout << "Inside derived class itself:" << '\n';
{
DerivedPrivVirtual::f();
DerivedPrivNonVirtual::f();
}
std::cout << '\n';
std::cout << "With non-dynamic variables:" << '\n';
{
DerivedPrivVirtual derivedPrivVirtual;
DerivedPrivNonVirtual derivedPrivNonVirtual;
}
std::cout << '\n';
}
Tanto GCC 4.7.1 y 3.1 CLANG dan la misma salida. Se llama al constructor de clase derivado, excepto cuando la clase derivada arroja un puntero de clase derivado a la clase base y es delete
s.
Además de este caso que parece bastante infrecuente y fácilmente evitable (el autor de la clase es el único que puede hacer daño, pero sabe de qué clase deriva su), ¿puedo concluir que es seguro?
With explicit derived pointer type:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor
With base pointer type:
Inside derived class itself:
DerivedPrivVirtual's dtor
BaseVirtual's dtor
BaseNonVirtual's dtor <-- Only a problem inside the class itself
With non-dynamic variables:
DerivedPrivNonVirtual's dtor
BaseNonVirtual's dtor
DerivedPrivVirtual's dtor
BaseVirtual's dtor
Bono pregunta: ¿qué pasa con la herencia protegida? Supongo que la capacidad de hacer daño ya no es prerrogativa para el autor de la clase derivada directamente, sino para los autores de cualquier clase en la jerarquía.
Si no recuerdo mal, Scott Meyers (autor de C++ efectivo, C++ más efectivo) aún no sabe qué significa herencia protegida. Public es una relación "is-a", Private está "implementado en términos de", pero ¿Protegido? Eso es un poco aterrador. – Borgleader