2015-12-12 19 views
20

Seit C++ 11 neigen Entwickler aus verschiedenen Gründen dazu, intelligente Zeigerklassen für Objekte mit dynamischer Lebensdauer zu verwenden. Und mit diesen neuen Smart-Pointer-Klassen, Standards, schlagen Sie sogar vor, keine Operatoren wie new zu verwenden, stattdessen schlagen Sie vor, make_shared oder make_unique zu verwenden, um einige fehleranfällige zu vermeiden.Wie übergebe ich Deleter zu make_shared?

Wenn wir ein Smart-Pointer-Klasse verwenden möchten, wie shared_ptr, können wir ein wie konstruieren können,

shared_ptr<int> p(new int(12)); 

Auch möchten wir eine benutzerdefinierte deleter passieren Zeigerklassen zu einem intelligenten,

shared_ptr<int> p(new int(12), deleter); 

Auf der anderen Seite, wenn wir make_shared verwenden möchten, z. int, statt Verwendung new und shared_ptr Konstruktor, wie auf dem ersten Ausdruck oben, wir

auto ip = make_shared<int>(12); 

Aber was nutzen können, wenn wir auch gerne eine individuelle deleter zu make_shared passieren, gibt es einen richtigen Weg, das zu tun? Scheint wie Compiler, mindestens gcc, gibt einen Fehler an,

auto ip = make_shared<int>(12, deleter); 
+0

Schreiben Sie Ihre eigene 'make_shared()', die dies unterstützt, es ist machbar. – user1095108

Antwort

30

Wie andere bereits gesagt haben, kann make_shared nicht mit einem benutzerdefinierten Deleter verwendet werden. Aber ich möchte erklären warum.

Benutzerdefinierte Deleter existieren, weil Sie den Zeiger auf eine spezielle Weise zugewiesen haben, und daher müssen Sie in der Lage sein, ihn entsprechend zu delozieren. Nun, make_shared weist den Zeiger mit new zu. Objekte, die mit new zugewiesen sind, sollten mit delete freigegeben werden. Was der Standard Deleter pflichtgemäß tut.

Kurz gesagt, wenn Sie mit dem Standard-Zuweisungsverhalten leben können, können Sie mit der Standard-Freigabe auch Verhalten zu leben. Und wenn Sie nicht mit dem Standardzuordnungsverhalten leben können, sollten Sie allocate_shared verwenden, die den bereitgestellten Zuordner verwendet, um den Speicher zuzuweisen und die Zuordnung aufzuheben.

Auch make_shared darf (und fast sicher wird) den Speicher für T und den Steuerblock für die shared_ptr innerhalb der gleichen Zuordnung zuweisen. Dies ist etwas, über das Ihr Deleter nicht wirklich Bescheid wissen oder sich damit befassen kann. In der Erwägung, allocate_shared ist in der Lage, es zu handhaben, da der von Ihnen zur Verfügung gestellte Zuteiler Zuteilung und Freigabe Aufgaben tun kann.

+2

Upvoted, darüber habe ich noch nie nachgedacht, aber es macht durchaus Sinn. Guter Hinweis. – skypjack

+0

+1 aber benutzerdefinierte Deleters sind nicht nur für die Steuerung der Freigabe, der Zeiger von einem 'shared_ptr 'wurde nicht unbedingt" allokiert "zugeordnet. Sie können zum Beispiel 'fclose' verwenden, um eine FILE * zu schließen. Mit 'allocate_shared' können Sie die Speicherzuweisung steuern, aber keine Ressourcen verwenden. Sie müssen immer noch ein Objekt konstruieren und zerstören, keine "Ressource" von einer Funktion erhalten und es mit einer anderen (Lösch-) Funktion freigeben. Selbst wenn [lwg 2070] (http://cplusplus.github.io/LWG/lwg-active.html#2070) behoben wird, können benutzerdefinierte deleter mit make/allocate_shared immer noch nicht zugelassen werden. –

+2

Die Quintessenz ist, dass make/allocate_shared es Ihnen nicht erlaubt, den Schritt "Eine Ressource erhalten" zu ändern, da es keinen Sinn ergibt, benutzerdefinierte Möglichkeiten zu nutzen, um die Ressource zu "befreien". Sie dienen zum einfachen Erstellen/Zerstören einer Objektverwendung von 'shared_ptr'. –

4

Sie können nicht. make_shared<T> leitet die bereitgestellten Argumente an den Konstruktor vom Typ T weiter. Es wird für den einfachen Fall verwendet, wenn Sie den Standardlöscher verwenden möchten.

10

Wie aus der documentation, make_shared akzeptiert eine Liste von Argumenten mit dem eine Instanz des T konstruiert werden.
Darüber hinaus sagt die Dokumentation, dass:

Diese Funktion typischerweise durch einen Aufruf zurück verwendet wird, um den Bau std :: shared_ptr (new T (args ...)) von einem gemeinsamen Zeiger aus dem rohen Zeiger zu ersetzen zu neu.

Aus diesem Grund kann man ableiten, dass Sie nicht eine benutzerdefinierte deleter einstellen.
Um dies zu tun, müssen Sie die shared_ptr für sich selbst mit Hilfe der richtigen constructor erstellen.
Als Beispiel für einen Konstruktor aus der vorgeschlagenen Liste können Sie verwenden:

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

So wird der Code so etwas wie:

auto ptr = std::shared_ptr(new MyClass{arg1, arg2}, myDeleter); 

Statt:

auto ptr = std::make_shared<MyClass>(arg1, arg2); 
3

Es nicht spezifiziert ist, wie make_shared den Speicher für das Objekt erhält (es könnte operator new oder malloc oder irgendeine Art von allocator verwenden), so gibt es keine Möglichkeit, eine benutzerdefinierte deleter wissen kann, wie das Richtige zu tun. make_shared erstellt das Objekt, also müssen Sie sich auch darauf verlassen, das Objekt richtig zu zerstören und die entsprechende Säuberung vorzunehmen, was auch immer das ist.

Auch möchten wir eine benutzerdefinierte deleter zu Smart-Pointer-Klassen passieren,

shared_ptr<int> p(new int(12), deleter);

Ich glaube nicht, dass dies ein sehr realistisches Beispiel ist. Ein benutzerdefinierter Löschvorgang wird normalerweise verwendet, wenn die Ressource auf eine bestimmte Weise abgerufen wurde. Wenn Sie es so einfach mit new erstellt haben, warum brauchen Sie dann einen benutzerdefinierten Deleter?

Wenn Sie möchten, dass nur ein Code auf Zerstörung ausgeführt wird, dann fügen Sie ihn in einen Destruktor ein! Auf diese Weise können Sie es auch weiterhin mit make_shared z.

struct RunSomethingOnDestruction { 
    RunSomethingOnDestruction(int n) : i(n) { } 
    ~RunSomethingOnDestruction() { /* something */ } 
    int i; 
}; 

auto px = std::make_shared<RunSomethingOnDestruction>(12); 
std:shared_ptr<int> p(px, px->i); 

Dies gibt Ihnen eine shared_ptr<int>, die von make_shared erstellt wird (so dass Sie die Speicheroptimierungen durch make_shared getan), die einige benutzerdefinierte Code auf Zerstörung laufen wird.