Welche C ++ Smart Pointer-Implementierungen sind verfügbar?

121

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.

AJG85
quelle
5
Ich denke, dies sollte als Antwort auf diese Frage erneut veröffentlicht und die Frage in eine tatsächliche Frage umgewandelt werden. Ansonsten spüre ich, dass die Leute dies als "keine wirkliche Frage" schließen werden.
Strager
3
Es gibt alle möglichen anderen intelligenten Zeiger, z. B. die ATL-intelligenten Zeiger oder OpenSceneGraphsosg::ref_ptr .
James McNellis
11
Gibt es hier eine Frage?
Cody Gray
6
Ich denke, dass Sie falsch verstanden haben std::auto_ptr. std::auto_ptr_refist ein Designdetail von std::auto_ptr. std::auto_ptrhat 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_ptrkann 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.
CB Bailey
3
Sie sagen, dass Sie kein Experte für intelligente Zeiger sind, aber Ihre Zusammenfassung ist ziemlich vollständig und korrekt (mit Ausnahme des kleinen Streits 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.
Konrad Rudolph

Antworten:

231

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 zur deleteZerstö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 wird std::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_ptrwar 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_ptrEs wird ziemlich ähnlich sein, außer mit den wichtigsten Verbesserungen, um die Schwächen std::auto_ptrwie 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 vorherige std::auto_ptr.

std::shared_ptr- Ich glaube, dies basiert auf TR1 und wurde boost::shared_ptrverbessert, 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_sharedkann verwendet werden, um eine std::shared_ptrZuordnung mit einem Heap mithilfe des Standardzuweisers effizient zu erstellen .

std::weak_ptr- Ebenfalls basierend auf TR1 und boost::weak_ptr. Dies ist eine Referenz auf ein Objekt, das a gehört, std::shared_ptrund verhindert daher nicht das Löschen des Objekts, wenn der std::shared_ptrReferenzzähler auf Null fällt. Um Zugriff auf den Rohzeiger zu erhalten, müssen Sie zuerst auf den std::shared_ptrAufruf zugreifen , lockder ein Leerzeichen zurückgibt, std::shared_ptrwenn 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 von std::weak_ptr, basierend auf dieser Implementierung, ermöglicht dies einen nicht besitzenden Verweis auf a boost::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 zur boost::shared_ptrVerwendung entwickelt wurde. Dies ist std::auto_ptrinsbesondere 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_ptrist in der Regel effizienter als shared_ptrda es Ihnen ermöglicht, eine einzelne Heap-Zuordnung pro Objekt zu haben. (Danke Arvid)

boost::shared_array- Dies ist ein boost::shared_ptrfür Arrays. Im Grunde genommen new [], operator[]und natürlich delete []werden gebacken. Diese verwendet in STL - Containern werden kann , und soweit ich weiß , tut alles boost:shared_ptrtut , obwohl Sie nicht verwenden können , boost::weak_ptrmit diesen. Sie können jedoch alternativ a boost::shared_ptr<std::vector<>>für ähnliche Funktionen verwenden und die Fähigkeit zur Verwendung boost::weak_ptrfür Referenzen wiedererlangen .

boost::scoped_array- Dies ist ein boost::scoped_ptrfür Arrays. Wie bei boost::shared_arrayallen 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önnten std::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 Sie boost::arraystattdessen in Betracht .


Qt

QPointer- In Qt 4.0 eingeführt, ist dies ein "schwacher" intelligenter Zeiger, der nur mit QObjectund 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önnen isNull()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 einer boost::intrusive_ptrintegrierten Thread-Sicherheit vergleichbar ist. Sie müssen jedoch Referenzzählmethoden ( refund deref) angeben, die Sie durch Unterklassen ausführen können QSharedData. 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, QSharedDataPointeraußer dass es nicht implizit aufruft detach(). Ich würde diese Version 2.0 QSharedDataPointerals 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_ptrobwohl wahrscheinlich deutlich mehr Overhead wie bei vielen Objekten in Qt.

QWeakPointer- Spüren Sie ein wiederkehrendes Muster? Genau wie std::weak_ptrund boost::weak_ptrdies wird in Verbindung mit verwendet, QSharedPointerwenn 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 im boost::scoped_ptrGegensatz 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 Aufwand QSharedPointerdie Kompatibilität, den ausnahmesicheren Code und alle Dinge, die Sie möglicherweise verwenden std::auto_ptroder boost::scoped_ptrfür die Sie möglicherweise verwenden, besser geeignet macht .

AJG85
quelle
1
Zwei Dinge, die meiner Meinung nach erwähnenswert sind: Sie intrusive_ptrsind in der Regel effizienter als shared_ptr, da Sie damit eine einzelne Heap-Zuordnung pro Objekt vornehmen können. shared_ptrwird im allgemeinen Fall ein separates kleines Heap-Objekt für die Referenzzähler zuweisen. std::make_sharedkann verwendet werden, um das Beste aus beiden Welten zu bekommen. shared_ptrmit nur einer Heap-Zuordnung.
Arvid
Ich habe eine möglicherweise nicht verwandte Frage: Kann die Garbage Collection implementiert werden, indem einfach alle Zeiger durch shared_ptrs ersetzt werden? (Ohne das Auflösen von zyklischen Referenzen)
Seth Carnegie
@ Seth Carnegie: Nicht alle Zeiger verweisen auf etwas, das im kostenlosen Store zugewiesen ist.
In silico
2
@the_mandrill Aber es funktioniert, wenn der Destruktor der besitzenden Klasse in einer separaten Übersetzungseinheit (.cpp-Datei) als der Client-Code definiert ist, der im Pimpl-Idiom ohnehin angegeben ist. Da diese Übersetzungseinheit normalerweise die vollständige Definition des Pimpl kennt und daher sein Destruktor (wenn er auto_ptr zerstört) den Pimpl korrekt zerstört. Ich hatte auch Angst davor, als ich diese Warnungen sah, aber ich habe es versucht und es funktioniert (der Destruktor des Pimpl wird gerufen). PS.: Bitte benutze die @ -syntax, damit ich Antworten sehen kann.
Christian Rau
2
Die Nützlichkeit der Liste wurde durch Hinzufügen geeigneter Links zu Dokumenten erhöht.
ulidtko
1

Zusätzlich zu den angegebenen gibt es auch einige sicherheitsorientierte:

SaferCPlusPlus

mse::TRefCountingPointerist ein Referenzzähler wie ein intelligenter Zeiger std::shared_ptr. Der Unterschied besteht darin, dass mse::TRefCountingPointeres 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 wird std::shared_ptr, mse::TRefCountingPointerist die Verwendung ansonsten optimaler.

mse::TScopeOwnerPointerist ähnlich boost::scoped_ptr, funktioniert aber in Verbindung mit mse::TScopeFixedPointereiner "stark-schwachen" Zeigerbeziehung ähnlich wie std::shared_ptrund std::weak_ptr.

mse::TScopeFixedPointerzeigt 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::TRegisteredPointerverhä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. Da mse::TRegisteredPointeres 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.)

Noah
quelle