2014-03-02 6 views
9

Betrachten Sie dieses Beispiel:Warum prüft unique-ptr die Basisklasse nicht auf virtual destructible?

#include <cstdio> 
#include <memory> 

struct base 
{ 
    base(int i): i(i) { printf("base ctor\n"); } 
    ~base() {  printf("base non-virtual dtor\n"); } // non-virtual 
    int i; 
}; 

struct derived : public base 
{ 
    char* s; 
    derived(int i): base(i), s(new char[i]) 
    { 
     printf("derived ctor\n"); 
    } 
    ~derived() 
    { 
     printf("derived dtor\n"); 
     delete [] s; 
    } 
}; 

int main() 
{ 
    printf("Success\n"); 

    //raw pointer 
    printf("RAW-POINTER\n"); 
    { 
     base* b = new derived(2); 
     // ...... 
     delete b; //here memory leak, but it's old- and error-prone code. 
    } 
    printf("---END-RAW-POINTER--\n"); 

    //unique-ptr 
    printf("UNIQUE_PTR\n"); 
    { 
     // I would that, this doesn't compile, because base- has not virtual destructor. 
     std::unique_ptr<base> bu(new derived(3)); // here still memory leak !!!! 
    } 
    printf("--END-UNIQUE_PTR--\n"); 


    return 0; 
} 

Der Code std::unique_ptr<base> bu(new derived(3)); einfach mit std::has_virtual_destructor Typ-Merkmale verbieten. Live code

Warum wird der obige Code kompiliert? Ist das standardmäßig erlaubt?

EDIT: interessant, aber mit std :: shared_ptr gearbeitet, das heißt sowohl Basis- und abgeleitete dtor nennen:

printf("SHARED_PTR\n"); 
    { 
     std::shared_ptr<base> b(new derived(3)); 
    } 
    printf("--END-SHARED_PTR--\n"); 

Output: 
SHARED_PTR 
base ctor 
derived ctor 
derived dtor 
base non-virtual dtor 
--END-SHARED_PTR-- 

Warum std :: shared_ptr dervied Klasse dtor nennen kann, aber std :: unique_ptr kann nicht ???

EDIT2: simple Ich brauche so etwas wie:

template< typename T, typename D = default_deleter<T> > 
class unique_ptr{ 
    ............. 

    template< typename U > 
    unique_ptr(U* u) if (U != T && T - is class && T is base of U, and D - is default_deleter, and T - has not virtual destructor) then = delete this ctor. 

}; 
+0

@Brian Bi: Nicht alle benutzerdefinierten deleter, in vielen Fällen Benutzer Grenzen mit default_deleter. – Khurshid

+2

'std :: shared_ptr' funktioniert. Es wird den Destruktor der Basis aufgerufen und abgeleitet: http://ideone.com/Nc542V – Brandon

+0

Wenn Sie 'public' Vererbung verwenden, sollten Sie Destruktor als' virtual' definieren. (http://stackoverflow.com/questions/270917/why-should-i-declare-a-virtual-destructor-for-an-abastract-class-in-c) – zahir

Antwort

8

Der Unterschied zwischen unique_ptr und shared_ptr in der Sprache des Standard ist, in Bezug auf ihre Destruktoren (und Konstrukteure). Diese Sprache für die Löscher der beiden Smart-Pointer, die auf Ihrem Beispiel gilt, ist ähnlich, aber auf subtile Weise anders:

 
[20.7.1.2.2] unique_ptr destructor 
... If get() == nullptr there are no efects. Otherwise get_deleter()(get()). 

[20.7.2.2.2] shared_ptr destructor 
... if *this owns an object p and a deleter d, d(p) is called. 

Sie, dass in beiden Fällen sehen die Standard sagt der deleter zu nennen, ist der Unterschied allerdings, wie sich die Deleter wird entschieden, und das unique_ptr löscht die Zeiger es erhält über get(), während shared_ptr löscht die Objekt. Diese Unterscheidung ist wichtig. Schauen Sie, wie die Konstrukteure für beide Klassen sind auch unterschiedlich:

Die shared_ptr ist wie folgt definiert:

template <class T> 
class shared_ptr { 
... 
    template<class Y> explicit shared_ptr(Y* p); 

Während die unique_ptr explizite Argument Konstruktor ist,

template <class T, class D = default_delete<T>> 
class unique_ptr { 
... 
    explicit unique_ptr(pointer p) noexcept; 
... 

Beachten Sie, dass unique_ptr nur Ruft den Standarddelete für den Typ ab, der in Ihrem Fall die Ebene delete ist, und speichert den Zeiger. Der shared_ptr<T> Konstruktor ist jedoch nicht auf T (!) Gestempelt, sondern auf den Typ des Objekts Y, mit dem er erstellt wurde. So in Ihrem Szenario

std::shared_ptr<base> b(new derived(3)); 

die shared_ptr mit T=base aber Y=derived aufgebaut werden, so dass explizit das abgeleitete Objekt zu zerstören, und nicht den Speicher in Ihrem Beispiel undicht.


Während Sie nicht den Standard ändern können, was Sie tun können, ist entweder aus unique_ptr in Ihrem Projekt erben oder Ihre eigenen Wrapper das gewünschte Verhalten zu erzwingen. Zum Beispiel:

namespace { 
    template <class T> 
    struct checked_delete : public std::default_delete<T> { 
     static_assert(std::has_virtual_destructor<T>::value, ""); 
    };  

    template <class T, class D = checked_delete<T>, class U> 
    std::unique_ptr<T, D> 
    make_unique_ptr(U* p) { return std::unique_ptr<T, D>(p, D()); }  
} 

// now this won't compile, because checked_delete<base> will not compile: 
auto bu = make_unique_ptr<base>(new derived(3)); 
+0

Ihr letztes Beispiel ist eine sehr nette Lösung, aber ich muss virtuelles dtor nur überprüfen, wenn es aus abgeleiteten Klassen erstellt wurde, nicht in jedem Fall. Hovewer, vielen Dank. Jetzt weiß ich, wie es funktioniert. – Khurshid

2

Nicht alle unique_pointers verwendet werden polymorph:

std::unique_ptr<int> p(new int(42)); 

Dies wäre nicht mit der Einschränkung, kompilieren Sie vorschlagen. Das Gleiche gilt für Klassen:

std::unique_ptr<YourClassHere> p(new YourClassHere); 
+0

int - ist kein Klassentyp. siehe is_class: http://en.cppreference.com/w/cpp/types/is_class – Khurshid

+0

Ihr zweites Beispiel, der Template-Parameter unique_ptr und der Parameterkonstruktor - sind dieselben Klassen. Was passiert, wenn es unterschiedliche Klassen sind? Wenn in diesem Fall die Basisklasse keinen virtuellen Destraktor hat, wird der Destruktor der abgeleiteten Klasse nicht aufgerufen. – Khurshid

+0

Welches ist genau das gleiche Verhalten, das Sie mit rohen Zeigern erhalten. – fredoverflow