2012-09-17 9 views
12

Rozumiem, że przy dziedziczeniu publicznym ogólnie jest to , a nie bezpieczne, ponieważ gdy delete ma wskaźnik klasy bazowej, kompilator generuje tylko kod do wywołania destruktora klasy podstawowej, a klasa pochodna nie jest wywoływana.Czy można bezpiecznie dziedziczyć z klasy za pomocą nie wirtualnego destruktora?

Ale dla prywatnego dziedziczenia klient nie może rzucić pochodzący klasy wskaźnik do wskaźnika klasy bazowej (jako prywatne dziedziczenie nie modelować is-a związek), więc delete jest zawsze używany na wskaźniku klasy pochodnej, a kompilator powinien być w stanie zobaczyć, że istnieje również klasa bazowa i wywołać jej destruktor.

że wykonany test:

#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'; 
} 

Zarówno GCC 4.7.1 i szczęk 3,1 daje taki sam efekt. Wydzielony konstruktor klasy jest wywoływany z wyjątkiem sytuacji, gdy klasa pochodna sama rzuca wskaźnik klasy pochodnej do klasy bazowej i delete.

Poza tym przypadkiem, który wydaje się dość rzadki i łatwy do uniknięcia (autor klasy jest jedynym facetem, który może wyrządzić szkodę, ale wie, z której klasy pochodzi jego), czy mogę stwierdzić, że jest on bezpieczny?

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 

Bonus pytanie: co o chronionej dziedziczenia? Przypuszczam, że zdolność czynienia szkód nie jest już właściwa dla bezpośrednio wywodzącego się autora klasy, ale dla autorów dowolnej klasy w hierarchii.

+0

Jeśli dobrze pamiętam, Scott Meyers (autor Effective C++, bardziej efektywny C++) nadal nie wie nawet, co oznacza dziedziczenie chronione. Public jest relacją "is-a", Private jest "zaimplementowany-w-warunkach", ale chroniony? To trochę przerażające. – Borgleader

Odpowiedz

9

Niezależnie od tego, czy dziedziczenie ma charakter publiczny czy prywatny, nie wpływa to na bezpieczeństwo kodu, a jedynie ogranicza zakres bezpiecznego/niefachowego użytkowania. Masz ten sam podstawowy problem: jeśli twoja klasa lub przyjaciel twojej klasy przekazuje obiekt twojego typu do interfejsu, który przenosi wskaźnik do bazy bez wirtualnego destruktora i jeśli ten interfejs przejmuje własność twojego obiektu, to tworzysz niezdefiniowany zachowanie.

Problem w projekcie polega na tym, że zgodnie z Twoim pytaniem, BaseNonVirtual nie jest przeznaczony do przedłużenia. Jeśli tak, powinien mieć publiczny wirtualny destruktor lub chroniony, nie-wirtualny, zapewniając, że żaden kod nie będzie mógł wywołać delete na obiekcie pochodnym przez wskaźnik do bazy.

+0

Wszelkie konstrukty mogą być nadużywane. Czy można bezpiecznie deklarować prywatnego członka danych? Oh noes, możesz przypadkowo ustawić go tam, gdzie nie przystoi! "Bezpieczny" oznacza zwykle "nie może być wykorzystywany przez innych", a prywatne dziedziczenie jest równie bezpieczne jak prywatny członek danych. –

+0

@ n.m .: Jeśli uważnie przeczytasz akapit pierwszy, powinieneś zauważyć, że wspomina on przypadki, w których konstrukcja ta zawiedzie: * jeśli twoja klasa [..] przekazuje obiekt twojego typu do ... *. Wspomina również o tym, co rozwiązanie tej dziury znajduje się w drugim akapicie. Prywatne dziedziczenie jest tak samo bezpieczne jak prywatny element danych, o ile nie dostarczasz wskaźników członkom, ale jest to rzadkie. Z drugiej strony, dziedziczenie jest często używane do wskazywania podstawy - jeżeli dziedziczenie jest (ab) używane do uzyskania funkcjonalności, która jest innym zagadnieniem konstrukcyjnym. –

0

Istnieje przypadek, gdy kod klienta może obsady Pochodzące do Bazy mimo prywatnego dziedziczenia:

delete reinterpret_cast<BaseNonVirtual*>(new DerivedPrivNonVirtual);

Zatem pomijając wykonanie ~DerivedPrivNonVirtual().

Ale biorąc pod uwagę, jak bardzo zniechęca się korzystanie z reinterpret_cast, możesz dojść do wniosku, że jest on "wystarczająco bezpieczny" dla twoich celów.

Powiązane problemy