Ok, also das letzte Mal, als ich C++ schrieb, um meinen Lebensunterhalt zu verdienen, std::auto_ptr
war alles, was die Standardbibliothek zur Verfügung hatte, und boost::shared_ptr
war der letzte Schrei. Ich habe mich nie wirklich mit den anderen zur Verfügung gestellten Smart Pointer-Typen beschäftigt. Ich verstehe, dass C++ 11 jetzt einige der Typen bietet, die Boost hervorgebracht hat, aber nicht alle.
Hat jemand einen einfachen Algorithmus, um zu bestimmen, wann welcher Smart Pointer verwendet werden soll? Am besten mit Hinweisen zu dummen Zeigern (rohen Zeigern wie T*
) und den Rest der Boost-Smart-Pointer. (Etwas wie this wäre toll).
Geteilte Eigentümerschaft:
Der shared_ptr
Und weak_ptr
, Die vom Standard übernommen wurden, sind fast identisch mit ihren Boost-Gegenstücken . Verwenden Sie sie, wenn Sie eine Ressource teilen müssen und nicht wissen, welche als letzte am Leben sein wird. Verwenden Sie weak_ptr
, Um die gemeinsam genutzte Ressource zu beobachten, ohne ihre Lebensdauer zu beeinflussen, und nicht, um Zyklen zu unterbrechen. Zyklen mit shared_ptr
Sollten normalerweise nicht passieren - zwei Ressourcen können sich nicht gegenseitig besitzen.
Beachten Sie, dass Boost zusätzlich shared_array
anbietet, was eine geeignete Alternative zu shared_ptr<std::vector<T> const>
Sein könnte.
Als nächstes bietet Boost intrusive_ptr
, eine einfache Lösung, wenn Ihre Ressource bereits eine Verwaltung mit Referenzzählung bietet und Sie diese auf das RAII-Prinzip anwenden möchten. Dieser wurde vom Standard nicht übernommen.
Einzigartiges Eigentum:
Boost hat auch ein scoped_ptr
, das nicht kopierbar ist und für das Sie kein Deleter angeben können. std::unique_ptr
Ist boost::scoped_ptr
Für Steroide und sollte Ihre Standardeinstellung sein, wenn Sie einen intelligenten Zeiger benötigen. Es erlaubt Ihnen, ein Deleter in seinen Template-Argumenten anzugeben und ist beweglich , im Gegensatz zu boost::scoped_ptr
. Es ist auch in STL-Containern voll verwendbar, solange Sie keine Operationen verwenden, die (offensichtlich) kopierbare Typen benötigen.
Beachten Sie noch einmal, dass Boost eine Array-Version hat: scoped_array
, die der Standard vereinheitlicht, indem std::unique_ptr<T[]>
Eine teilweise Spezialisierung erfordert, die den Zeiger statt delete[]
delete
ing es (mit dem default_delete
r). std::unique_ptr<T[]>
Bietet auch operator[]
Anstelle von operator*
Und operator->
An.
Beachten Sie, dass std::auto_ptr
Immer noch im Standard enthalten ist, aber nicht mehr unterstützt wird . §D.10 [depr.auto.ptr]
Die Klassenvorlage
auto_ptr
Ist veraltet. [ Hinweis: Die Klassenvorlageunique_ptr
(20.7.1) bietet eine bessere Lösung. - Endnote ]
Kein Besitz:
Verwenden Sie dumme Zeiger (rohe Zeiger) oder Verweise auf nicht im Besitz befindliche Verweise auf Ressourcen, und wenn Sie wissen, dass die Die Ressource überlebt das referenzierende Objekt/den referenzierenden Bereich. Bevorzugen Sie Verweise und verwenden Sie unformatierte Zeiger, wenn Sie eine Null- oder Rücksetzbarkeit benötigen.
Wenn Sie einen nicht im Besitz befindlichen Verweis auf eine Ressource wünschen, aber nicht wissen, ob die Ressource das Objekt, das auf sie verweist, überlebt, packen Sie die Ressource in einen shared_ptr
Und verwenden Sie einen weak_ptr
- Sie können testen, ob das übergeordnete Element shared_ptr
mit lock
am Leben ist. Dies gibt ein shared_ptr
zurück, das nicht null ist, wenn die Ressource noch vorhanden ist. Wenn Sie testen möchten, ob die Ressource tot ist, verwenden Sie expired
. Die beiden mögen sich ähnlich anhören, sind jedoch bei gleichzeitiger Ausführung sehr unterschiedlich, da expired
nur den Rückgabewert für diese einzelne Anweisung garantiert. Ein scheinbar unschuldiger Test wie
if(!wptr.expired())
something_assuming_the_resource_is_still_alive();
ist eine potenzielle Rennbedingung.
Die Entscheidung, welcher Smart Pointer verwendet werden soll, ist eine Frage von Eigentumsrecht. Wenn es um die Ressourcenverwaltung geht, ist Objekt A besitzt Objekt B, wenn es die Lebensdauer von Objekt B steuert. Beispielsweise gehören Mitgliedsvariablen ihren jeweiligen Objekten, da die Lebensdauer von Mitgliedsvariablen gebunden ist auf die Lebensdauer des Objekts. Sie wählen intelligente Zeiger basierend auf dem Besitz des Objekts.
Beachten Sie, dass das Eigentum an einem Softwaresystem vom Eigentum getrennt ist, wie wir es außerhalb von Software betrachten würden. Zum Beispiel könnte eine Person ihr Haus "besitzen", aber das bedeutet nicht unbedingt, dass ein Person
- Objekt die Kontrolle über die Lebensdauer eines House
- Objekts hat. Wenn Sie diese Konzepte der realen Welt mit Softwarekonzepten kombinieren, können Sie sich auf sichere Weise selbst in ein Loch programmieren.
Wenn Sie das alleinige Eigentum an dem Objekt haben, verwenden Sie std::unique_ptr<T>
.
Wenn Sie das Eigentum an dem Objekt geteilt haben ...
- Wenn sich keine Zyklen im Besitz befinden, verwenden Sie std::shared_ptr<T>
.
- Wenn es Zyklen gibt, definieren Sie eine "Richtung" und verwenden Sie std::shared_ptr<T>
in eine Richtung und std::weak_ptr<T>
in dem anderen.
Wenn das Objekt Ihnen gehört, Sie aber möglicherweise keinen Eigentümer haben, verwenden Sie normale Zeiger T*
(z. B. übergeordnete Zeiger).
Wenn das Objekt Sie besitzt (oder anderweitig eine garantierte Existenz hat), verwenden Sie Referenzen T&
.
Vorsichtsmaßnahme: Beachten Sie die Kosten für intelligente Zeiger. In speicher- oder leistungsbeschränkten Umgebungen kann es vorteilhaft sein, nur normale Zeiger mit einem manuelleren Schema für die Speicherverwaltung zu verwenden.
Die Kosten:
std::shared_ptr
hat den Overhead eines Referenzzählungsinkrements beim Kopieren, plus eines Dekrements beim Zerstören, gefolgt von einer 0-Zählungsprüfung mit Löschen des gehaltenen Objekts. Abhängig von der Implementierung kann dies Ihren Code aufblähen und Leistungsprobleme verursachen.Beispiele:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
Ein binärer Baum besitzt keinen übergeordneten Baum, aber die Existenz eines Baums impliziert die Existenz seines übergeordneten Baums (oder nullptr
für root), sodass ein normaler Zeiger verwendet wird. Ein binärer Baum (mit Wertsemantik) hat das alleinige Eigentum seiner Kinder, das sind also std::unique_ptr
.
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Hier besitzt der Listenknoten seine nächste und vorherige Liste, also definieren wir eine Richtung und verwenden shared_ptr
für next und weak_ptr
for prev, um den Zyklus zu unterbrechen.
Verwenden Sie die ganze Zeit unique_ptr<T>
, Es sei denn, Sie benötigen eine Referenzzählung. In diesem Fall verwenden Sie shared_ptr<T>
(Und in sehr seltenen Fällen weak_ptr<T>
, Um Referenzzyklen zu verhindern). In fast allen Fällen ist übertragbares Alleinstellungsmerkmal in Ordnung.
Rohe Zeiger: Nur gut, wenn Sie kovariante Renditen benötigen und keine eigenen Zeiger, die auftreten können. Ansonsten sind sie nicht besonders nützlich.
Array-Zeiger: unique_ptr
Hat eine Spezialisierung für T[]
, Die automatisch delete[]
Für das Ergebnis aufruft, sodass Sie beispielsweise sicher unique_ptr<int[]> p(new int[42]);
ausführen können. shared_ptr
Sie benötigen immer noch einen benutzerdefinierten Löscher, aber keinen speziellen gemeinsamen oder eindeutigen Array-Zeiger. Natürlich werden solche Dinge normalerweise sowieso am besten durch std::vector
Ersetzt. Leider bietet shared_ptr
Keine Array-Zugriffsfunktion, sodass Sie get()
immer noch manuell aufrufen müssen, aber unique_ptr<T[]>
Bietet operator[]
Anstelle von operator*
Und operator->
. In jedem Fall muss man sich grenzenlos kontrollieren. Dies macht shared_ptr
Etwas weniger benutzerfreundlich, obwohl der allgemeine Vorteil und die fehlende Abhängigkeit von Boosts unique_ptr
Und shared_ptr
Erneut zu den Gewinnern machen.
Bereichsspezifische Zeiger: Durch unique_ptr
Irrelevant gemacht, genau wie auto_ptr
.
Da ist wirklich nichts mehr dran. In C++ 03 ohne Verschiebungssemantik war diese Situation sehr kompliziert, aber in C++ 11 ist der Rat sehr einfach.
Es gibt noch Verwendungen für andere intelligente Zeiger wie intrusive_ptr
Oder interprocess_ptr
. Sie sind jedoch sehr Nischen und im allgemeinen Fall völlig unnötig.
Fälle, in denen unique_ptr
Verwendet werden soll:
Fälle, in denen shared_ptr
Verwendet werden soll:
Fälle, in denen weak_ptr
Verwendet werden soll:
Fühlen Sie sich frei, um mehr zu bearbeiten und hinzuzufügen