2012-09-17 18 views
12

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.

+0

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

Respuesta

9

Ya sea que la herencia sea pública o privada no afecta la seguridad del código, solo limita el alcance en el que se puede usar de manera segura/insegura. Usted tiene el mismo problema básico: si su clase o un amigo de su clase pasa un objeto de su tipo a una interfaz que toma un puntero a la base sin destructor virtual, y si esa interfaz adquiere la propiedad de su objeto, entonces está creando undefined comportamiento.

El problema en el diseño es que según su pregunta, el BaseNonVirtual no está diseñado para extenderse. Si lo fuera, debería tener un destructor virtual público o uno no virtual protegido, lo que garantiza que ningún código podrá llamar a eliminar en un objeto derivado a través de un puntero a la base.

+0

Cualquier construcción puede ser mal utilizada. ¿Es seguro declarar un miembro de datos privados? Oh, no, ¡puedes colocarlo accidentalmente donde no esté aporopriate! "Seguro" normalmente significa "no estar abierto al abuso de otros" y la herencia privada es tan segura como un miembro de datos privados. –

+0

@ n.m .: Si lees cuidadosamente el primer párrafo, debes tener en cuenta que menciona los casos en los que fallará este constructo: * si tu clase [...] pasa un objeto de tu tipo a ... *. También menciona cuál es la solución para este agujero en el segundo párrafo. La herencia privada es tan segura como un miembro de datos privados, siempre y cuando no genere sugerencias para los miembros, pero eso no es común. Por otro lado, la herencia se usa a menudo para dar punteros a la base, si la herencia se usa (ab) para obtener la funcionalidad que es un problema de diseño diferente. –

0

Hay un caso en el que el código de cliente puede colada derivados a base pesar de la herencia privada:

delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);

Así saltarse ejecución de ~DerivedPrivNonVirtual().

Pero, dado cuánto se desaconseja el uso de reinterpret_cast, puede concluir que es "lo suficientemente seguro" para sus propósitos.

Cuestiones relacionadas