Smart Pointer (Boost) erklärt


220

Was ist der Unterschied zwischen den folgenden Zeigern? Wann verwenden Sie jeden Zeiger im Produktionscode, wenn überhaupt?

Beispiele wären willkommen!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Verwenden Sie Boost im Produktionscode?

Antworten:


339

Grundlegende Eigenschaften von Smart Pointern

Es ist einfach, wenn Sie Eigenschaften haben, denen Sie jeden intelligenten Zeiger zuweisen können. Es gibt drei wichtige Eigenschaften.

  • überhaupt kein Eigentum
  • Eigentumsübergang
  • Eigentumsanteil

Der erste bedeutet, dass ein intelligenter Zeiger das Objekt nicht löschen kann, weil er es nicht besitzt. Der zweite bedeutet, dass immer nur ein intelligenter Zeiger gleichzeitig auf dasselbe Objekt zeigen kann. Wenn der Smart Pointer von Funktionen zurückgegeben werden soll, wird der Besitz beispielsweise auf den zurückgegebenen Smart Pointer übertragen.

Der dritte bedeutet, dass mehrere intelligente Zeiger gleichzeitig auf dasselbe Objekt zeigen können. Dies gilt auch für einen Rohzeiger. Rohzeigern fehlt jedoch eine wichtige Funktion: Sie definieren nicht, ob sie Eigentümer sind oder nicht. Ein Smart Pointer mit Eigentumsanteil löscht das Objekt, wenn jeder Eigentümer das Objekt aufgibt. Dieses Verhalten wird häufig benötigt, sodass Smart Pointer mit gemeinsamem Besitz weit verbreitet sind.

Einige Smart Pointer unterstützen weder den zweiten noch den dritten. Sie können daher nicht von Funktionen zurückgegeben oder an eine andere Stelle übergeben werden. Dies ist am besten für RAIIZwecke geeignet, bei denen der Smart Pointer lokal gehalten und nur erstellt wird, damit ein Objekt freigegeben wird, nachdem es den Gültigkeitsbereich verlassen hat.

Der Eigentumsanteil kann durch einen Kopierkonstruktor implementiert werden. Dadurch wird natürlich ein intelligenter Zeiger kopiert, und sowohl die Kopie als auch das Original verweisen auf dasselbe Objekt. Die Übertragung des Eigentums kann derzeit in C ++ nicht wirklich implementiert werden, da es keine Möglichkeit gibt, etwas von einem Objekt auf ein anderes zu übertragen, das von der Sprache unterstützt wird: Wenn Sie versuchen, ein Objekt von einer Funktion zurückzugeben, geschieht, dass das Objekt kopiert wird. Ein intelligenter Zeiger, der die Übertragung des Eigentums implementiert, muss den Kopierkonstruktor verwenden, um diese Übertragung des Eigentums zu implementieren. Dies unterbricht jedoch wiederum seine Verwendung in Containern, da die Anforderungen ein bestimmtes Verhalten des Kopierkonstruktors von Elementen von Containern angeben, das mit diesem sogenannten "Verschiebungskonstruktor" -Verhalten dieser intelligenten Zeiger nicht kompatibel ist.

C ++ 1x bietet native Unterstützung für die Übertragung des Eigentums, indem sogenannte "Verschiebungskonstruktoren" und "Verschiebungszuweisungsoperatoren" eingeführt werden. Es kommt auch mit einem solchen Smart-Point-of-Ownership-Zeiger namens unique_ptr.

Kategorisieren von intelligenten Zeigern

scoped_ptrist ein intelligenter Zeiger, der weder übertragbar noch gemeinsam nutzbar ist. Es ist nur verwendbar, wenn Sie lokal Speicher zuweisen müssen, aber stellen Sie sicher, dass er wieder freigegeben wird, wenn er außerhalb des Gültigkeitsbereichs liegt. Es kann jedoch weiterhin mit einem anderen scoped_ptr ausgetauscht werden, wenn Sie dies wünschen.

shared_ptrist ein intelligenter Zeiger, der das Eigentum teilt (dritte Art oben). Es wird als Referenz gezählt, damit es sehen kann, wann die letzte Kopie den Gültigkeitsbereich verlässt, und dann das verwaltete Objekt freigibt.

weak_ptrist ein nicht besitzender Smart Pointer. Es wird verwendet, um auf ein verwaltetes Objekt (verwaltet von einem shared_ptr) zu verweisen, ohne einen Referenzzähler hinzuzufügen. Normalerweise müssten Sie den Rohzeiger aus dem shared_ptr herausholen und diesen kopieren. Dies wäre jedoch nicht sicher, da Sie nicht überprüfen können, wann das Objekt tatsächlich gelöscht wurde. Schwache_ptr bietet also Mittel, indem sie auf ein von shared_ptr verwaltetes Objekt verweist. Wenn Sie auf das Objekt zugreifen müssen, können Sie die Verwaltung sperren (um zu vermeiden, dass in einem anderen Thread ein shared_ptr es freigibt, während Sie das Objekt verwenden) und es dann verwenden. Wenn der schwache_ptr auf ein bereits gelöschtes Objekt verweist, werden Sie durch Auslösen einer Ausnahme bemerkt. Die Verwendung von schwachem_ptr ist am vorteilhaftesten, wenn Sie eine zyklische Referenz haben: Die Referenzzählung kann mit einer solchen Situation nicht einfach umgehen.

intrusive_ptrist wie ein shared_ptr, behält jedoch nicht die Referenzanzahl in einem shared_ptr bei, sondern überlässt das Inkrementieren / Dekrementieren der Anzahl auf einige Hilfsfunktionen, die von dem verwalteten Objekt definiert werden müssen. Dies hat den Vorteil, dass ein bereits referenziertes Objekt (dessen Referenzanzahl durch einen externen Referenzzählmechanismus erhöht wird) in ein intrusive_ptr eingefügt werden kann, da der Referenzzähler nicht mehr intern im Smart Pointer ist, der Smart Pointer jedoch einen vorhandenen verwendet Referenzzählmechanismus.

unique_ptrist ein Zeiger auf die Übertragung des Eigentums. Sie können es nicht kopieren, aber Sie können es mithilfe der Verschiebungskonstruktoren von C ++ 1x verschieben:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Dies ist die Semantik, der std :: auto_ptr gehorcht, aber aufgrund der fehlenden nativen Unterstützung für das Verschieben werden sie nicht ohne Fallstricke bereitgestellt. unique_ptr stiehlt automatisch Ressourcen von einem temporären anderen unique_ptr, was eines der Hauptmerkmale der Verschiebungssemantik ist. auto_ptr wird in der nächsten C ++ Standard-Version zugunsten von unique_ptr nicht mehr unterstützt. In C ++ 1x können auch Objekte gestopft werden, die nur beweglich, aber nicht in Container kopierbar sind. So können Sie beispielsweise unique_ptrs in einen Vektor einfügen. Ich werde hier aufhören und Sie auf einen schönen Artikel darüber verweisen, wenn Sie mehr darüber lesen möchten.


3
Danke für das Lob, Alter. Ich schätze es, also wirst du jetzt auch +1 bekommen: p
Johannes Schaub - litb

@litb: Ich habe Zweifel an der "Übertragung des Eigentums"; Ich stimme gibt es keine wirkliche Übertragung des Eigentums unter Objekten in C ++ 03, aber für Smart - Pointer kann dies nicht getan werden, durch die destruktive Kopie Mechanismus hier angegebenen informit.com/articles/article.aspx?p=31529&seqNum= 5 .
Legends2k

3
fantastische Antwort. Hinweis: auto_ptrist bereits veraltet (C ++ 11).
Nickolay

2
"Dies wiederum unterbricht die Verwendung in Containern, da die Anforderungen ein bestimmtes Verhalten des Kopierkonstruktors von Elementen von Containern angeben, das mit diesem sogenannten" Moving-Konstruktor "-Verhalten dieser intelligenten Zeiger nicht kompatibel ist." Ich habe diesen Teil nicht bekommen.
Raja

Mir wurde auch gesagt, dass intrusive_ptrdies shared_ptrfür eine bessere Cache-Kohärenz vorzuziehen ist . Anscheinend funktioniert der Cache besser, wenn Sie den Referenzzähler als Teil des Speichers des verwalteten Objekts selbst anstelle eines separaten Objekts speichern. Dies kann in einer Vorlage oder Oberklasse des verwalteten Objekts implementiert werden.
Eliot

91

scoped_ptr ist das einfachste. Wenn es den Rahmen verlässt, wird es zerstört. Der folgende Code ist unzulässig (scoped_ptrs können nicht kopiert werden), veranschaulicht jedoch einen Punkt:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr wird als Referenz gezählt. Jedes Mal, wenn eine Kopie oder Zuordnung erfolgt, wird der Referenzzähler erhöht. Jedes Mal, wenn der Destruktor einer Instanz ausgelöst wird, wird der Referenzzähler für das rohe T * dekrementiert. Sobald es 0 ist, wird der Zeiger freigegeben.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

schwach_ptr ist eine schwache Referenz auf einen gemeinsam genutzten Zeiger, bei der Sie überprüfen müssen, ob der gemeinsam genutzte Zeiger noch vorhanden ist

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr wird normalerweise verwendet, wenn Sie ein Smart-Ptr eines Drittanbieters verwenden müssen. Es wird eine kostenlose Funktion zum Hinzufügen und Verringern des Referenzzählers aufgerufen. Weitere Informationen finden Sie unter dem Link zum Erhöhen der Dokumentation.


soll nicht if (tPtrAccessed[0].get() == 0)sein if (tPtrAccessed.get() == 0) ?
Rajeshwar

@ DougT. Glauben Sie, dass Java dieselbe Idee mit Referenzen verwendet? Weich, hart, schwach usw.?
Gansub

20

Übersehen Sie boost::ptr_containerin keiner Umfrage zu Boost Smart Pointern. Sie können in Situationen von unschätzbarem Wert sein, in denen ein zB std::vector<boost::shared_ptr<T> >zu langsam wäre.


Als ich es das letzte Mal ausprobierte, zeigte das Benchmarking, dass sich die Leistungslücke deutlich geschlossen hatte, seit ich dies ursprünglich geschrieben hatte, zumindest auf einem typischen PC HW! Der effizientere ptr_container-Ansatz kann jedoch in Nischenanwendungsfällen noch einige Vorteile haben.
Tag

12

Ich stimme dem Rat zu, die Dokumentation zu lesen. Es ist nicht so beängstigend, wie es scheint. Und ein paar kurze Hinweise:

  • scoped_ptr- Ein Zeiger wird automatisch gelöscht, wenn er den Gültigkeitsbereich verlässt. Hinweis - Keine Zuordnung möglich, aber kein Overhead
  • intrusive_ptr- Referenzzählzeiger ohne Overhead von smart_ptr. Das Objekt selbst speichert jedoch den Referenzzähler
  • weak_ptr- arbeitet mit zusammen shared_ptr, um die Situationen zu bewältigen, die zu zirkulären Abhängigkeiten führen (lesen Sie die Dokumentation und suchen Sie auf Google nach schönen Bildern;)
  • shared_ptr - der generische, leistungsstärkste (und schwerste) der intelligenten Zeiger (von denen, die durch Boost angeboten werden)
  • Es gibt auch alte auto_ptr, die sicherstellen, dass das Objekt, auf das es zeigt, automatisch zerstört wird, wenn die Steuerung einen Bereich verlässt. Es hat jedoch eine andere Kopiersemantik als der Rest der Jungs.
  • unique_ptr- wird mit C ++ 0x kommen

Antwort zum Bearbeiten: Ja


8
Ich bin hierher gekommen, weil ich die Boost-Dokumentation zu beängstigend fand.
Francois Botha
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.