2014-10-05 2 views
5

std::shared_ptr hat einen raffinierten Template-Konstruktor, der automatisch den richtigen Deleter für seinen gegebenen Typ erzeugt (Konstruktor # 2 in diesem Link).Gibt es eine bequeme Möglichkeit, unique_ptr automatisch zu einem Deleter wie shared_ptr zu bekommen?

Bis gerade jetzt, ich (fälschlicherweise) dachte std::unique_ptr ein ähnliches Konstruktor hatte, aber als ich lief den folgenden Code:

#include <memory> 
#include <iostream> 

// Notice nothing is virtual 
struct Foo 
{ 
    ~Foo() { std::cout << "Foo\n"; } 
}; 

struct Bar : public Foo 
{ 
    ~Bar() { std::cout << "Bar\n"; } 
}; 

int main() 
{ 
    { 
     std::cout << "shared_ptr:\n"; 
     std::shared_ptr<Foo> p(new Bar()); // prints Bar Foo 
    } 

    { 
     std::cout << "unique_ptr:\n"; 
     std::unique_ptr<Foo> p(new Bar()); // prints Foo 
    } 
} 

I was surprised to learn that unique_ptr doesn't call Bar's destructor.

Was ist eine saubere, einfache und korrekte Art und Weise zu erstellen a, die den richtigen Deleter für den angegebenen Zeiger hat? Vor allem, wenn ich eine ganze Liste von diesen speichern möchte (d. H. std::vector<std::unique_ptr<Foo>>), was bedeutet, dass sie alle einen heterogenen Typ haben müssen?

(verzeihen Sie den schlechten Titel, fühlen sich frei, einen besseren vorzuschlagen)

+0

'deleter 'unique_ptr' ist in seinem Typ. Wenn Sie einen Vektor von 'std :: unique_ptr ' erstellen, dann werden alle 'std :: default_delete ' verwenden. –

+0

http://stackoverflow.com/questions/6829576/why-does-unique-ptr-have-the-deleter-as-a-type-parameter-while-shared-ptr-doesn –

+3

Ich denke, das Problem ist mit Ihrem Klasse, nicht 'std :: unique_ptr'. Ihr Destruktor muss virtuell sein. – Galik

Antwort

5

Hier einen Weg ist:

{ 
    std::cout << "unique_ptr<Bar, void(void*)>:\n"; 
    std::unique_ptr<Foo, void(*)(void*)> p(
     new Bar(), [](void*p) -> void { delete static_cast<Bar*>(p); } 
     ); // prints Bar Foo 
} 

Ein Hauptproblem bei diesem Ansatz ist, dass unique_ptr Konvertierung unterstützt auf logisch „Zeiger auf Basis Klasse ", aber der Standard garantiert nicht, dass die Konvertierung nach void* dann die gleiche Adresse ergibt. In der Praxis ist das nur ein Problem, wenn die Basisklasse nicht-polymorph ist, während die abgeleitete Klasse polymorph ist, indem ein vtable ptr eingeführt wird und dadurch möglicherweise das Speicherlayout ein wenig verändert wird. Aber in dieser möglichen - aber nicht wahrscheinlichen - Situation würde die Rückübertragung in den Deleter einen falschen Zeigerwert und einen Knall ergeben.

Also, das oben genannte ist nicht formal sicher in Bezug auf solche Konvertierungen.


ungefähr das gleiche tun wie ein shared_ptr tut (shared_ptr unterstützt Konvertierungen logische Zeiger-to-Base), würden Sie auch die ursprünglichen void* Zeiger, zusammen mit dem deleter speichern müssen.


Im Allgemeinen, wenn Sie die oberste Basisklasse steuern, machen Sie ihren Destruktor virtuell.

Das kümmert sich um alles.

+0

Für das problematische Szenario, wie wäre es mit doppelter 'static_cast' - Cast die' void * 'zu' Foo * 'zuerst und dann zu' Bar * '? –

+0

@ T.C. Das Problem ist, dass der Deleter den Zeigertyp nicht kennt, der an das 'void *' übergeben wurde. Je nach Typ kann er auf verschiedene Adressen verweisen. Aber jetzt, da du mich darüber nachdenkst, ist das Problem nur, dass 'void *' zu viele Informationen verwirft. Ich denke, alles wäre in Ordnung mit 'Foo *' Argument Typ stattdessen, dann auf 'Bar * 'im Deleter gegossen. Oder gibt es ein Problem damit?Es ist ein bisschen spät am Tag für mich. –

+0

Der an den Deleter übergebene Zeiger ist nicht immer nützlich, auch wenn die Typen OK sind. Manchmal kann static_cast unmöglich sein, zum Beispiel bei virtueller Vererbung (http://stackoverflow.com/questions/7484913/why-cant-static-cast-be-used-to-down-cast-when-virtual-inheritance-is- beteiligt). Ich denke, die einzige wirklich robuste Lösung besteht darin, dass der Deleter sich den ursprünglichen Zeigerwert merkt (wie er von "neu" zurückgegeben wurde). Dies bedeutet, dass der Deleter sein Argument formell ignoriert und daher auch "void *" sein kann. Schließlich, wenn Sie diesen Speicher "Preis" zahlen, können Sie auch eine "deep_copy" ohne zusätzliche Kosten hinzufügen. –

7

Sie sollten den Destruktor von Foovirtual machen. Das ist eine gute Übung, unabhängig davon, ob Sie unique_ptr verwenden oder nicht. Das wird auch das Problem behandeln, mit dem Sie es zu tun haben.

+0

Ich stimme zu, dass das getan werden sollte, aber in diesem Fall kann ich die Klassen nicht ändern – Cornstalks

Verwandte Themen