2014-04-08 5 views
6

In der jüngsten overload journal unter dem Thema Erzwingen der Regel von Null, beschreiben die Autoren, wie wir die Regel von fünf Betreibern Schreiben vermeiden können, da die Gründe für das Schreiben von ihnen sind:C++ Rule of Zero: polymorphen Löschen und unique_ptr Verhalten

  1. Ressourcenmanagement
  2. Polymorphe Löschen

Und beide können diese Sorge durch die Verwendung Smart Pointer genommen werden.

Hier interessiert mich speziell der zweite Teil.

Betrachten Sie den folgenden Code-Schnipsel:

class Base 
{ 
public: 
    virtual void Fun() = 0; 
}; 


class Derived : public Base 
{ 
public: 

    ~Derived() 
    { 
     cout << "Derived::~Derived\n"; 
    } 

    void Fun() 
    { 
     cout << "Derived::Fun\n"; 
    } 
}; 


int main() 
{ 
    shared_ptr<Base> pB = make_shared<Derived>(); 
    pB->Fun(); 
} 

In diesem Fall, wie die Autoren des Artikels erklären, wir polymorphe Löschen erhalten, indem einen gemeinsamen Zeiger verwenden, und dies funktioniert. Wenn ich die shared_ptr durch eine unique_ptr ersetze, kann ich die polymorphe Löschung nicht mehr beobachten.

Jetzt ist meine Frage, warum sind diese beiden Verhaltensweisen unterschiedlich? Warum kümmert sich shared_ptr polymorphe Löschung, während unique_ptr nicht?

+0

Wie initialisierst du den 'unique_pointer'? –

+0

Spielt es eine Rolle? Wie immer: unique_ptr pB (new Derived()) – Arun

+6

Weil 'std :: shared_ptr' einen Zeiger auf die Deleter-Funktion trägt. Wenn Sie einem kompatiblen einen 'std :: shared_ptr 'zuweisen, ist der Zeiger eines der kopierten oder verschobenen Elemente. Dies passiert nicht mit 'std :: unique_ptr' und da Ihre Basisklasse keinen virtuellen Destruktor hat, werden Sie entbeint. –

Antwort

3

Sie haben Ihre Antwort hier: https://stackoverflow.com/a/22861890/2007142

Zitat:

Sobald die letzte shared_ptr Bezug den Gültigkeitsbereich verlässt oder zurückgesetzt wird, ~Derived() aufgerufen wird, und der Speicher freigegeben. Daher müssen Sie ~Base() nicht virtuell machen. unique_ptr<Base> und make_unique<Derived> bieten diese Funktion nicht, da sie die Mechanik von shared_ptr in Bezug auf den Deleter nicht bereitstellen, da der eindeutige Zeiger viel einfacher ist und auf den niedrigsten Overhead abzielt und daher den für den Deleter benötigten zusätzlichen Funktionszeiger nicht speichert .

+0

noch können Sie einen anderen Deletertyp als der Standard einrichten, und es scheint zu lesen, als ob es um kopiert wird .. . – Yakk

1
template<typename T> 
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>; 

template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) { 
    return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}}; 
} 

Das Problem ist, dass die Standard-deleter für unique_ptr ruft delete auf den gespeicherten Zeiger. Obiges speichert einen Deleter, der den Typ bei der Konstruktion kennt. Wenn er also in die Basisklasse kopiert wird, wird unique_ptr immer noch als Kind gelöscht.

Das fügt bescheidenen Overhead hinzu, da wir einen Zeiger dereferenzieren müssen. Außerdem denormalisiert er den Typ, da die standardmäßig erstellten smart_unique_ptr s nun illegal sind. Sie können dies mit etwas zusätzlicher Arbeit beheben (ersetzen Sie einen Raw-Funktionszeiger durch einen halbklugen Funktor, der zumindest nicht abstürzt: Der Funktionszeiger sollte jedoch aktiviert sein, wenn unique nicht leer ist, wenn der Deleter aufgerufen wird) .

+0

Ich habe eine Implementierung von dieser Antwort http://stackoverflow.com/a/13512344/602798 verwendet und gibt mir immer noch keine polymorphe Löschung – Arun

+0

@Arun Wo ist Ihr virtuelles dtor? –

+0

@Arun, warum hast du kein virtuelles dtor in Base? – ooga

2

Es funktioniert, wenn Sie die C++ 14 make_unique verwenden oder schreiben Sie Ihre eigene wie in Yakk's Antwort. Grundsätzlich ist der Unterschied zwischen dem gemeinsamen Zeiger Verhalten ist, dass Sie bekommt:

template< 
    class T, 
    class Deleter = std::default_delete<T> 
> class unique_ptr; 

für unique_pointer und wie Sie sehen können, gehören die deleter nach Art. Wenn Sie eine unique_pointer<Base> deklarieren, wird immer std::default_delete<Base> als Standard verwendet. Aber make_unique wird sich darum kümmern, den richtigen Deleter für Ihre Klasse zu verwenden.

Wenn shared_ptr mit euch:

template< class Y, class Deleter > 
shared_ptr(Y* ptr, Deleter d); 

und andere Überlastungen als Konstruktor. Wie Sie sehen können, hängt der Standard-Deleter für unique_ptr vom Template-Parameter ab, wenn er den Typ deklariert (außer Sie verwenden make_unique), während der Deleter für shared_ptr von dem Typ abhängt, der an den Konstruktor übergeben wird.


können Sie eine Version sehen, die ohne virtuelle destructor polymorphen löschen können here (diese Version sollte auch in VS2012 arbeiten). Beachten Sie, dass es ein bisschen zusammen gehackt ist und ich bin mir momentan nicht sicher, wie das Verhalten von unique_ptr und make_shared in C++ 14 aussehen wird, aber ich hoffe, dass sie das einfacher machen werden. Vielleicht werde ich in die Papiere für die C++ 14 Ergänzungen schauen und sehen, ob sich etwas geändert hat, wenn ich später die Zeit habe.

+0

Bedeutet dies, dass, wenn ich die shared_ptr wie diese shared_ptr pB (new Derived()) erstellen; Ich werde keine polymorphe Löschung bekommen? – Arun

+0

Ich habe versucht, eine shared_ptr wie in meinem vorherigen Kommentar erwähnt zu erstellen. Aber ich bekomme immer noch polymorphe Löschung (BTW ich benutze VS2012 hoffe, es ist nicht einige VS spezifische Nicht-Standard-Verhalten) – Arun