Vergleiche, Vor- und Nachteile und wann zu verwenden?
Dies ist ein Spin-off aus einem Garbage Collection-Thread, in dem eine meiner Meinung nach einfache Antwort viele Kommentare zu bestimmten Smart-Pointer-Implementierungen generierte. Es schien also sinnvoll, einen neuen Beitrag zu beginnen.
Letztendlich stellt sich die Frage, welche verschiedenen Implementierungen von Smart Pointern in C ++ es gibt und wie sie miteinander verglichen werden. Nur einfache Vor- und Nachteile oder Ausnahmen und Fallstricke für etwas, von dem Sie sonst denken könnten, dass es funktionieren sollte.
Ich habe einige Implementierungen veröffentlicht, die ich verwendet oder zumindest beschönigt und als Antwort unten in Betracht gezogen habe, und mein Verständnis für ihre Unterschiede und Ähnlichkeiten, die möglicherweise nicht 100% genau sind. Sie können mich also nach Bedarf überprüfen oder korrigieren.
Das Ziel ist es, einige neue Objekte und Bibliotheken kennenzulernen oder meine Verwendung und mein Verständnis bestehender Implementierungen zu korrigieren, die bereits weit verbreitet sind, und eine anständige Referenz für andere zu erhalten.
quelle
osg::ref_ptr
.std::auto_ptr
.std::auto_ptr_ref
ist ein Designdetail vonstd::auto_ptr
.std::auto_ptr
hat nichts mit Garbage Collection zu tun, sondern dient hauptsächlich dazu, eine ausnahmesichere Eigentumsübertragung zu ermöglichen, insbesondere in Situationen mit Funktionsaufruf und Funktionsrückgabe.std::unique_ptr
kann nur die "Probleme" lösen, die Sie mit Standardcontainern zitieren, da C ++ geändert wurde, um eine Unterscheidung zwischen Verschieben und Kopieren zu ermöglichen, und Standardcontainer geändert wurden, um dies auszunutzen.auto_ptr_ref
, ein Implementierungsdetail zu sein). Dennoch stimme ich zu, dass Sie dies als Antwort posten und die Frage so umformulieren sollten, dass sie eine tatsächliche Frage ist. Dies kann dann als zukünftige Referenz dienen.Antworten:
C ++ 03
std::auto_ptr
- Vielleicht eines der Originale, bei denen das erste Entwurfssyndrom auftrat, das nur begrenzte Möglichkeiten zur Müllabfuhr bietet. Der erste Nachteil ist, dass es zurdelete
Zerstörung führt, was sie für das Halten von Array-zugewiesenen Objekten inakzeptabel macht (new[]
). Es übernimmt den Besitz des Zeigers, sodass zwei automatische Zeiger nicht dasselbe Objekt enthalten sollten. Die Zuweisung überträgt das Eigentum und setzt den automatischen Zeiger rvalue auf einen Nullzeiger zurück. Was zum vielleicht schlimmsten Nachteil führt; Sie können aufgrund der oben genannten Unfähigkeit, kopiert zu werden, nicht in STL-Containern verwendet werden. Der letzte Schlag für jeden Anwendungsfall ist, dass sie im nächsten Standard von C ++ veraltet sein werden.std::auto_ptr_ref
- Dies ist kein intelligenter Zeiger, sondern ein Designdetail, das in Verbindung mit verwendet wirdstd::auto_ptr
, um das Kopieren und Zuweisen in bestimmten Situationen zu ermöglichen. Insbesondere kann es verwendet werden, um eine Nicht- Konstantestd::auto_ptr
in einen l-Wert umzuwandeln, indem der Colvin-Gibbons-Trick verwendet wird, der auch als Verschiebungskonstruktor bezeichnet wird , um das Eigentum zu übertragen.Im Gegenteil, vielleicht
std::auto_ptr
war es nicht wirklich als universeller intelligenter Zeiger für die automatische Speicherbereinigung gedacht. Die meisten meiner begrenzten Erkenntnisse und Annahmen basieren auf Herb Sutters effektiver Nutzung von auto_ptr, und ich verwende sie regelmäßig, wenn auch nicht immer optimal .C ++ 11
std::unique_ptr
- Dies ist unser Freund, der es ersetzen wird.std::auto_ptr
Es wird ziemlich ähnlich sein, außer mit den wichtigsten Verbesserungen, um die Schwächenstd::auto_ptr
wie das Arbeiten mit Arrays, den Wertschutz über einen privaten Kopierkonstruktor, die Verwendung mit STL-Containern und -Algorithmen usw. zu korrigieren und Speicherbedarf sind begrenzt. Dies ist ein idealer Kandidat zum Ersetzen oder besser als Besitz von Rohzeigern. Wie das "Einzigartige" impliziert, gibt es nur einen Besitzer des Zeigers, genau wie der vorherigestd::auto_ptr
.std::shared_ptr
- Ich glaube, dies basiert auf TR1 und wurdeboost::shared_ptr
verbessert, um auch Aliasing und Zeigerarithmetik einzuschließen. Kurz gesagt, es wird ein intelligenter Zeiger mit Referenzzählung um ein dynamisch zugewiesenes Objekt gewickelt. Da "gemeinsam genutzt" impliziert, dass der Zeiger mehr als einem gemeinsam genutzten Zeiger gehören kann, wenn die letzte Referenz des letzten gemeinsam genutzten Zeigers den Gültigkeitsbereich verlässt, wird das Objekt entsprechend gelöscht. Diese sind auch threadsicher und können in den meisten Fällen unvollständige Typen verarbeiten.std::make_shared
kann verwendet werden, um einestd::shared_ptr
Zuordnung mit einem Heap mithilfe des Standardzuweisers effizient zu erstellen .std::weak_ptr
- Ebenfalls basierend auf TR1 undboost::weak_ptr
. Dies ist eine Referenz auf ein Objekt, das a gehört,std::shared_ptr
und verhindert daher nicht das Löschen des Objekts, wenn derstd::shared_ptr
Referenzzähler auf Null fällt. Um Zugriff auf den Rohzeiger zu erhalten, müssen Sie zuerst auf denstd::shared_ptr
Aufruf zugreifen ,lock
der ein Leerzeichen zurückgibt,std::shared_ptr
wenn der eigene Zeiger abgelaufen ist und bereits zerstört wurde. Dies ist in erster Linie nützlich, um unbegrenzte Anzahl hängender Referenzen zu vermeiden, wenn mehrere intelligente Zeiger verwendet werden.Boost
boost::shared_ptr
- Wahrscheinlich am einfachsten in den unterschiedlichsten Szenarien (STL, PIMPL, RAII usw.) zu verwenden. Dies ist ein gemeinsam genutzter, referenzierter, gezählter Smart Pointer. Ich habe in einigen Situationen einige Beschwerden über Leistung und Overhead gehört, aber ich muss sie ignoriert haben, weil ich mich nicht erinnern kann, was das Argument war. Anscheinend war es populär genug, um ein ausstehendes Standard-C ++ - Objekt zu werden, und es fallen keine Nachteile gegenüber der Norm in Bezug auf intelligente Zeiger ein.boost::weak_ptr
- Ähnlich wie bei der vorherigen Beschreibung vonstd::weak_ptr
, basierend auf dieser Implementierung, ermöglicht dies einen nicht besitzenden Verweis auf aboost::shared_ptr
. Sie rufen nicht überraschend auf,lock()
um auf den "starken" gemeinsam genutzten Zeiger zuzugreifen, und müssen überprüfen, ob er gültig ist, da er möglicherweise bereits zerstört wurde. Stellen Sie nur sicher, dass der zurückgegebene freigegebene Zeiger nicht gespeichert wird, und lassen Sie ihn aus dem Gültigkeitsbereich verschwinden, sobald Sie damit fertig sind. Andernfalls kehren Sie direkt zum zyklischen Referenzproblem zurück, bei dem Ihre Referenzzählungen hängen bleiben und Objekte nicht zerstört werden.boost::scoped_ptr
- Dies ist eine einfache Smart-Pointer-Klasse mit geringem Overhead, die wahrscheinlich für eine leistungsfähigere Alternative zurboost::shared_ptr
Verwendung entwickelt wurde. Dies iststd::auto_ptr
insbesondere deshalb vergleichbar , weil es nicht sicher als Element eines STL-Containers oder mit mehreren Zeigern auf dasselbe Objekt verwendet werden kann.boost::intrusive_ptr
- Ich habe dies noch nie verwendet, aber meines Wissens ist es so konzipiert, dass es beim Erstellen eigener Smart-Pointer-kompatibler Klassen verwendet werden kann. Sie müssen die Referenzzählung selbst implementieren. Sie müssen auch einige Methoden implementieren, wenn Ihre Klasse generisch sein soll. Außerdem müssen Sie Ihre eigene Thread-Sicherheit implementieren. Auf der positiven Seite gibt Ihnen dies wahrscheinlich die individuellste Möglichkeit, genau auszuwählen, wie viel oder wie wenig "Schlauheit" Sie möchten.intrusive_ptr
ist in der Regel effizienter alsshared_ptr
da es Ihnen ermöglicht, eine einzelne Heap-Zuordnung pro Objekt zu haben. (Danke Arvid)boost::shared_array
- Dies ist einboost::shared_ptr
für Arrays. Im Grunde genommennew []
,operator[]
und natürlichdelete []
werden gebacken. Diese verwendet in STL - Containern werden kann , und soweit ich weiß , tut allesboost:shared_ptr
tut , obwohl Sie nicht verwenden können ,boost::weak_ptr
mit diesen. Sie können jedoch alternativ aboost::shared_ptr<std::vector<>>
für ähnliche Funktionen verwenden und die Fähigkeit zur Verwendungboost::weak_ptr
für Referenzen wiedererlangen .boost::scoped_array
- Dies ist einboost::scoped_ptr
für Arrays. Wie beiboost::shared_array
allen erforderlichen Arrays ist die Güte eingebrannt. Diese ist nicht kopierbar und kann daher nicht in STL-Containern verwendet werden. Ich habe fast überall gefunden, wo Sie dies verwenden möchten, was Sie wahrscheinlich nur verwenden könntenstd::vector
. Ich habe nie festgestellt, welches tatsächlich schneller ist oder weniger Overhead hat, aber dieses Array mit Gültigkeitsbereich scheint weitaus weniger involviert zu sein als ein STL-Vektor. Wenn Sie die Zuordnung auf dem Stapel beibehalten möchten, ziehen Sieboost::array
stattdessen in Betracht .Qt
QPointer
- In Qt 4.0 eingeführt, ist dies ein "schwacher" intelligenter Zeiger, der nur mitQObject
und abgeleiteten Klassen funktioniert. Dies ist im Qt-Framework fast alles, sodass dies keine wirkliche Einschränkung darstellt. Es gibt jedoch Einschränkungen, nämlich, dass es keinen "starken" Zeiger liefert. Obwohl Sie überprüfen können, ob das zugrunde liegende Objekt gültig ist, könnenisNull()
Sie feststellen, dass Ihr Objekt direkt nach dem Bestehen dieser Prüfung zerstört wird, insbesondere in Umgebungen mit mehreren Threads. Ich glaube, die Leute halten dies für veraltet.QSharedDataPointer
- Dies ist ein "starker" intelligenter Zeiger, der möglicherweise mit einerboost::intrusive_ptr
integrierten Thread-Sicherheit vergleichbar ist. Sie müssen jedoch Referenzzählmethoden (ref
undderef
) angeben, die Sie durch Unterklassen ausführen könnenQSharedData
. Wie bei einem Großteil von Qt werden die Objekte am besten durch umfangreiche Vererbung und Unterklassen verwendet. Alles scheint das beabsichtigte Design zu sein.QExplicitlySharedDataPointer
- Sehr ähnlich,QSharedDataPointer
außer dass es nicht implizit aufruftdetach()
. Ich würde diese Version 2.0QSharedDataPointer
als eine leichte Erhöhung der Kontrolle darüber bezeichnen, wann genau zu trennen ist, nachdem der Referenzzähler auf Null gefallen ist, ist kein ganz neues Objekt wert.QSharedPointer
- Atomic Reference Counting, Thread-sicherer, gemeinsam nutzbarer Zeiger, benutzerdefinierte Löschvorgänge (Array-Unterstützung), klingt wie alles, was ein intelligenter Zeiger sein sollte. Dies ist, was ich hauptsächlich als intelligenter Zeiger in Qt verwende, und ich finde es vergleichbar mit,boost:shared_ptr
obwohl wahrscheinlich deutlich mehr Overhead wie bei vielen Objekten in Qt.QWeakPointer
- Spüren Sie ein wiederkehrendes Muster? Genau wiestd::weak_ptr
undboost::weak_ptr
dies wird in Verbindung mit verwendet,QSharedPointer
wenn Sie Referenzen zwischen zwei intelligenten Zeigern benötigen, die andernfalls dazu führen würden, dass Ihre Objekte niemals gelöscht werden.QScopedPointer
- Dieser Name sollte auch vertraut aussehen und basierte tatsächlich imboost::scoped_ptr
Gegensatz zu den Qt-Versionen von gemeinsam genutzten und schwachen Zeigern. Es dient dazu, einen intelligenten Zeiger für einen einzelnen Eigentümer bereitzustellen, ohne dessen AufwandQSharedPointer
die Kompatibilität, den ausnahmesicheren Code und alle Dinge, die Sie möglicherweise verwendenstd::auto_ptr
oderboost::scoped_ptr
für die Sie möglicherweise verwenden, besser geeignet macht .quelle
intrusive_ptr
sind in der Regel effizienter alsshared_ptr
, da Sie damit eine einzelne Heap-Zuordnung pro Objekt vornehmen können.shared_ptr
wird im allgemeinen Fall ein separates kleines Heap-Objekt für die Referenzzähler zuweisen.std::make_shared
kann verwendet werden, um das Beste aus beiden Welten zu bekommen.shared_ptr
mit nur einer Heap-Zuordnung.shared_ptr
s ersetzt werden? (Ohne das Auflösen von zyklischen Referenzen)Es gibt auch Loki, das richtlinienbasierte Smart Pointer implementiert.
Weitere Verweise auf richtlinienbasierte intelligente Zeiger, die sich mit dem Problem der schlechten Unterstützung der Optimierung der leeren Basis sowie der Mehrfachvererbung durch viele Compiler befassen:
quelle
Zusätzlich zu den angegebenen gibt es auch einige sicherheitsorientierte:
SaferCPlusPlus
mse::TRefCountingPointer
ist ein Referenzzähler wie ein intelligenter Zeigerstd::shared_ptr
. Der Unterschied besteht darin, dassmse::TRefCountingPointer
es sicherer, kleiner und schneller ist, aber keinen Thread-Sicherheitsmechanismus hat. Und es gibt Versionen in den Versionen "nicht null" und "fest" (nicht retargetierbar), von denen sicher angenommen werden kann, dass sie immer auf ein gültig zugewiesenes Objekt verweisen. Wenn Ihr Zielobjekt also von asynchronen Threads gemeinsam genutzt wirdstd::shared_ptr
,mse::TRefCountingPointer
ist die Verwendung ansonsten optimaler.mse::TScopeOwnerPointer
ist ähnlichboost::scoped_ptr
, funktioniert aber in Verbindung mitmse::TScopeFixedPointer
einer "stark-schwachen" Zeigerbeziehung ähnlich wiestd::shared_ptr
undstd::weak_ptr
.mse::TScopeFixedPointer
zeigt auf Objekte, die auf dem Stapel zugeordnet sind oder deren "besitzender" Zeiger auf dem Stapel zugeordnet ist. Die Funktionalität ist (absichtlich) eingeschränkt, um die Sicherheit bei der Kompilierung ohne Laufzeitkosten zu verbessern. Der Punkt von "Scope" -Zeigern besteht im Wesentlichen darin, eine Reihe von Umständen zu identifizieren, die einfach und deterministisch genug sind, dass keine (Laufzeit-) Sicherheitsmechanismen erforderlich sind.mse::TRegisteredPointer
verhält sich wie ein Rohzeiger, außer dass sein Wert automatisch auf null_ptr gesetzt wird, wenn das Zielobjekt zerstört wird. In den meisten Situationen kann es als allgemeiner Ersatz für Rohzeiger verwendet werden. Wie ein Rohzeiger hat er keine intrinsische Thread-Sicherheit. Im Gegenzug ist es jedoch kein Problem, auf auf dem Stapel zugewiesene Objekte zu zielen (und den entsprechenden Leistungsvorteil zu erzielen). Wenn Laufzeitprüfungen aktiviert sind, ist dieser Zeiger vor dem Zugriff auf ungültigen Speicher geschützt. Damse::TRegisteredPointer
es das gleiche Verhalten wie ein Rohzeiger hat, wenn es auf gültige Objekte zeigt, kann es mit einer Direktive zur Kompilierungszeit "deaktiviert" (automatisch durch den entsprechenden Rohzeiger ersetzt) werden, sodass es beim Auffinden von Fehlern beim Debuggen / Testen verwendet werden kann / Beta-Modi, während im Release-Modus keine Overhead-Kosten anfallen.In diesem Artikel wird beschrieben, warum und wie sie verwendet werden. (Hinweis, schamloser Stecker.)
quelle