Ich habe angefangen, intelligente Zeiger von C ++ 11 zu studieren, und sehe keine nützliche Verwendung von std::weak_ptr
. Kann mir jemand sagen, wann std::weak_ptr
es nützlich / notwendig ist?
c++11
shared-ptr
weak-ptr
c++-faq
artm
quelle
quelle
Antworten:
Ein gutes Beispiel wäre ein Cache.
Für Objekte, auf die kürzlich zugegriffen wurde, möchten Sie sie im Speicher behalten, sodass Sie einen starken Zeiger auf sie halten. In regelmäßigen Abständen scannen Sie den Cache und entscheiden, auf welche Objekte in letzter Zeit nicht zugegriffen wurde. Sie müssen diese nicht im Speicher behalten, damit Sie den starken Zeiger loswerden.
Aber was ist, wenn dieses Objekt verwendet wird und ein anderer Code einen starken Zeiger darauf enthält? Wenn der Cache seinen einzigen Zeiger auf das Objekt entfernt, kann er ihn nie wieder finden. Der Cache behält also einen schwachen Zeiger auf Objekte bei, die er finden muss, wenn sie zufällig im Speicher bleiben.
Dies ist genau das, was ein schwacher Zeiger tut - er ermöglicht es Ihnen, ein Objekt zu lokalisieren, wenn es noch vorhanden ist, behält es jedoch nicht bei, wenn nichts anderes es benötigt.
quelle
std::weak_ptr
ist ein sehr guter Weg, um das Problem der baumelnden Zeiger zu lösen. Durch die Verwendung von Rohzeigern kann nicht festgestellt werden, ob die referenzierten Daten freigegeben wurden oder nicht. Stattdessen können die Benutzer die Gültigkeit der Daten durch Aufrufen von oder überprüfen , indem siestd::shared_ptr
die Daten verwalten lassen undstd::weak_ptr
den Benutzern die Daten zur Verfügung stellen .expired()
lock()
Sie können dies nicht
std::shared_ptr
alleine tun , da allestd::shared_ptr
Instanzen das Eigentum an den Daten teilen, die nicht entfernt werden, bevor alle Instanzen vonstd::shared_ptr
entfernt wurden. Hier ist ein Beispiel für die Überprüfung auf baumelnden Zeiger mithilfe vonlock()
:quelle
std::weak_ptr::lock
Erstellt eine neuestd::shared_ptr
, die das Eigentum an dem verwalteten Objekt teilt.Eine andere Antwort, hoffentlich einfacher. (für andere Googler)
Angenommen, Sie haben
Team
undMember
Objekte.Offensichtlich ist es eine Beziehung: Das
Team
Objekt hat Zeiger auf seineMembers
. Und es ist wahrscheinlich, dass die Mitglieder auch einen Rückzeiger auf ihrTeam
Objekt haben.Dann haben Sie einen Abhängigkeitszyklus. Wenn Sie verwenden
shared_ptr
, werden Objekte nicht mehr automatisch freigegeben, wenn Sie die Referenz auf sie aufgeben, da sie zyklisch aufeinander verweisen. Dies ist ein Speicherverlust.Sie brechen dies mit
weak_ptr
. Der "Eigentümer" verwendet normalerweiseshared_ptr
und der "Eigentümer" verwendet aweak_ptr
für sein übergeordnetes Element und konvertiert es vorübergehend in,shared_ptr
wenn er Zugriff auf sein übergeordnetes Element benötigt.Speichern Sie einen schwachen ptr:
Verwenden Sie es dann bei Bedarf
quelle
shared_ptr
ist, das Eigentum zu teilen, damit niemand die besondere Verantwortung hat, den Speicher freizugeben. Er wird automatisch freigegeben, wenn er nicht mehr verwendet wird. Es sei denn, es gibt eine Schleife ... Möglicherweise haben mehrere Teams denselben Spieler (frühere Teams?). Wenn das Teamobjekt die Mitglieder "besitzt", muss zunächst kein a verwendet werdenshared_ptr
.shared_ptr
von seinen "Teammitgliedern" referenziert wird, wann wird es zerstört? Was Sie beschreiben, ist ein Fall, in dem es keine Schleife gibt.Hier ist ein Beispiel, das mir von @jleahy gegeben wurde: Angenommen, Sie haben eine Sammlung von Aufgaben, die asynchron ausgeführt und von einem verwaltet werden
std::shared_ptr<Task>
. Möglicherweise möchten Sie in regelmäßigen Abständen etwas mit diesen Aufgaben tun, sodass ein Timer-Ereignis möglicherweise a durchläuftstd::vector<std::weak_ptr<Task>>
und den Aufgaben etwas zu tun gibt. Gleichzeitig kann eine Aufgabe gleichzeitig entschieden haben, dass sie nicht mehr benötigt wird, und sterben. Der Timer kann somit prüfen, ob die Aufgabe noch aktiv ist, indem er aus dem schwachen Zeiger einen gemeinsamen Zeiger erstellt und diesen gemeinsamen Zeiger verwendet, sofern er nicht null ist.quelle
Sie sind bei Boost.Asio nützlich, wenn Sie nicht garantiert sind, dass ein Zielobjekt noch vorhanden ist, wenn ein asynchroner Handler aufgerufen wird. Der Trick besteht darin, ein
weak_ptr
Objekt mitstd::bind
oder Lambda-Captures in das asynchrone Handler-Objekt einzubinden .Dies ist eine Variante der
self = shared_from_this()
Redewendung, die häufig in Boost.Asio-Beispielen verwendet wird, bei denen ein ausstehender asynchroner Handler die Lebensdauer des Zielobjekts nicht verlängert, jedoch dennoch sicher ist, wenn das Zielobjekt gelöscht wird.quelle
this
self = shared_from_this()
Idioms, wenn der Handler Methoden innerhalb derselben Klasse aufruft.shared_ptr : enthält das reale Objekt.
schwach_ptr : Wird verwendet
lock
, um eine Verbindung zum tatsächlichen Eigentümer herzustellen, oder gibtshared_ptr
andernfalls einen NULL- Wert zurück .Grob gesagt
weak_ptr
ähnelt die Rolle der Rolle der Wohnungsagentur . Ohne Agenten müssen wir möglicherweise zufällige Häuser in der Stadt überprüfen, um ein Haus zur Miete zu bekommen. Die Makler stellen sicher, dass wir nur die Häuser besuchen, die noch zugänglich und zu vermieten sind.quelle
weak_ptr
Es ist auch gut, das korrekte Löschen eines Objekts zu überprüfen - insbesondere bei Komponententests. Ein typischer Anwendungsfall könnte folgendermaßen aussehen:quelle
Bei der Verwendung von Zeigern ist es wichtig, die verschiedenen verfügbaren Zeigertypen zu verstehen und zu wissen, wann es sinnvoll ist, die einzelnen Zeiger zu verwenden. Es gibt vier Arten von Zeigern in zwei Kategorien:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
]
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
]
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
Raw-Zeiger (manchmal als "Legacy-Zeiger" oder "C-Zeiger" bezeichnet) bieten ein "Bare-Bones" -Zeigerverhalten und sind eine häufige Ursache für Fehler und Speicherlecks. Raw-Zeiger bieten keine Möglichkeit, den Besitz der Ressource zu verfolgen, und Entwickler müssen 'delete' manuell aufrufen, um sicherzustellen, dass sie keinen Speicherverlust verursachen. Dies wird schwierig, wenn die Ressource gemeinsam genutzt wird, da es schwierig sein kann zu wissen, ob noch Objekte auf die Ressource zeigen. Aus diesen Gründen sollten Rohzeiger generell vermieden und nur in leistungskritischen Abschnitten des Codes mit begrenztem Umfang verwendet werden.
Eindeutige Zeiger sind ein grundlegender intelligenter Zeiger, der den zugrunde liegenden Rohzeiger auf die Ressource "besitzt" und für das Aufrufen "Löschen" und das Freigeben des zugewiesenen Speichers verantwortlich ist, sobald das Objekt, dem der eindeutige Zeiger "gehört", den Gültigkeitsbereich verlässt. Der Name "eindeutig" bezieht sich auf die Tatsache, dass nur ein Objekt den eindeutigen Zeiger zu einem bestimmten Zeitpunkt "besitzen" darf. Das Eigentum kann über den Befehl move auf ein anderes Objekt übertragen werden, ein eindeutiger Zeiger kann jedoch niemals kopiert oder freigegeben werden. Aus diesen Gründen sind eindeutige Zeiger eine gute Alternative zu Rohzeigern, wenn zu einem bestimmten Zeitpunkt nur ein Objekt den Zeiger benötigt, und dies entlastet den Entwickler von der Notwendigkeit, am Ende des Lebenszyklus des besitzenden Objekts Speicher freizugeben.
Freigegebene Zeiger sind eine andere Art von intelligenten Zeigern, die eindeutigen Zeigern ähneln, jedoch ermöglichen, dass viele Objekte Eigentümer des gemeinsam genutzten Zeigers sind. Wie ein eindeutiger Zeiger sind gemeinsam genutzte Zeiger dafür verantwortlich, den zugewiesenen Speicher freizugeben, sobald alle Objekte auf die Ressource zeigen. Dies wird mit einer Technik erreicht, die als Referenzzählung bezeichnet wird. Jedes Mal, wenn ein neues Objekt den gemeinsamen Zeiger in Besitz nimmt, wird der Referenzzähler um eins erhöht. Wenn ein Objekt den Gültigkeitsbereich verlässt oder nicht mehr auf die Ressource zeigt, wird der Referenzzähler um eins verringert. Wenn der Referenzzähler Null erreicht, wird der zugewiesene Speicher freigegeben. Aus diesen Gründen sind gemeinsam genutzte Zeiger eine sehr leistungsstarke Art von intelligenten Zeigern, die immer dann verwendet werden sollten, wenn mehrere Objekte auf dieselbe Ressource verweisen müssen.
Schließlich sind schwache Zeiger eine andere Art von intelligentem Zeiger, der nicht direkt auf eine Ressource zeigt, sondern auf einen anderen Zeiger (schwach oder gemeinsam genutzt). Schwache Zeiger können nicht direkt auf ein Objekt zugreifen, aber sie können erkennen, ob das Objekt noch vorhanden ist oder ob es abgelaufen ist. Ein schwacher Zeiger kann vorübergehend in einen gemeinsam genutzten Zeiger konvertiert werden, um auf das Objekt zuzugreifen, auf das verwiesen wird (sofern es noch vorhanden ist). Betrachten Sie zur Veranschaulichung das folgende Beispiel:
In diesem Beispiel haben Sie einen schwachen Zeiger auf Besprechung B. Sie sind kein "Eigentümer" in Besprechung B, sodass diese ohne Sie enden kann, und Sie wissen nicht, ob sie beendet wurde oder nicht, es sei denn, Sie überprüfen. Wenn es nicht beendet ist, können Sie beitreten und teilnehmen, andernfalls können Sie nicht. Dies unterscheidet sich von einem gemeinsamen Zeiger auf Besprechung B, da Sie dann sowohl in Besprechung A als auch in Besprechung B ein "Eigentümer" sind (an beiden gleichzeitig teilnehmen).
Das Beispiel zeigt, wie ein schwacher Zeiger funktioniert und nützlich ist, wenn ein Objekt ein externer Beobachter sein muss , aber nicht die Verantwortung für die gemeinsame Nutzung des Eigentums übernehmen möchte. Dies ist besonders nützlich in dem Szenario, in dem zwei Objekte aufeinander zeigen müssen (auch als Zirkelverweis bezeichnet). Mit gemeinsam genutzten Zeigern kann kein Objekt freigegeben werden, da das andere Objekt immer noch stark auf sie zeigt. Wenn einer der Zeiger ein schwacher Zeiger ist, kann das Objekt, das den schwachen Zeiger enthält, bei Bedarf weiterhin auf das andere Objekt zugreifen, sofern es noch vorhanden ist.
quelle
Abgesehen von den anderen bereits erwähnten gültigen Anwendungsfällen
std::weak_ptr
ist ein großartiges Tool in einer Multithread-Umgebung, weilstd::shared_ptr
in Verbindung mitstd::weak_ptr
ist sicher gegen baumelnde Zeiger - im Gegensatz zustd::unique_ptr
in Verbindung mit rohen Zeigernstd::weak_ptr::lock()
ist eine atomare Operation (siehe auch Informationen zur Thread-Sicherheit von schwachem_ptr )Stellen Sie sich eine Aufgabe vor, um alle Bilder eines Verzeichnisses (~ 10.000) gleichzeitig in den Speicher zu laden (z. B. als Miniaturbild-Cache). Offensichtlich ist der beste Weg, dies zu tun, ein Steuerelement-Thread, der die Bilder verarbeitet und verwaltet, und mehrere Worker-Threads, die die Bilder laden. Das ist eine leichte Aufgabe. Hier ist eine sehr vereinfachte Implementierung (
join()
usw. wird weggelassen, die Threads müssten in einer realen Implementierung usw. anders behandelt werden)Es wird jedoch viel komplizierter, wenn Sie das Laden der Bilder unterbrechen möchten, z. B. weil der Benutzer ein anderes Verzeichnis ausgewählt hat. Oder auch wenn Sie den Manager zerstören wollen.
Sie benötigen eine Thread-Kommunikation und müssen alle Loader-Threads stoppen, bevor Sie Ihr
m_imageDatas
Feld ändern können . Andernfalls würden die Lader so lange geladen, bis alle Bilder fertig sind - auch wenn sie bereits veraltet sind. Im vereinfachten Beispiel wäre das nicht allzu schwierig, aber in einer realen Umgebung können die Dinge viel komplizierter sein.Die Threads wären wahrscheinlich Teil eines Thread-Pools, der von mehreren Managern verwendet wird, von denen einige gestoppt werden und andere nicht usw. Der einfache Parameter
imagesToLoad
wäre eine gesperrte Warteschlange, in die diese Manager ihre Image-Anforderungen von verschiedenen Steuer-Threads verschieben mit den Lesern, die die Anfragen - in beliebiger Reihenfolge - am anderen Ende platzieren. Und so wird die Kommunikation schwierig, langsam und fehleranfällig. Eine sehr elegante Möglichkeit, in solchen Fällen zusätzliche Kommunikation zu vermeiden, ist die Verwendungstd::shared_ptr
in Verbindung mitstd::weak_ptr
.Diese Implementierung ist fast so einfach wie die erste, benötigt keine zusätzliche Thread-Kommunikation und kann Teil eines Thread-Pools / einer Thread-Warteschlange in einer realen Implementierung sein. Da die abgelaufenen Bilder übersprungen und nicht abgelaufene Bilder verarbeitet werden, müssten die Threads während des normalen Betriebs niemals gestoppt werden. Sie können den Pfad jederzeit sicher ändern oder Ihre Manager zerstören, da der Leser fn prüft, ob der Besitzzeiger nicht abgelaufen ist.
quelle
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: schwach_ptr ist ein intelligenter Zeiger, der einen nicht besitzenden ("schwachen") Verweis auf ein Objekt enthält, das von std :: shared_ptr verwaltet wird. Es muss in std :: shared_ptr konvertiert werden, um auf das referenzierte Objekt zugreifen zu können.
std :: schwach_ptr modelliert temporären Besitz: Wenn auf ein Objekt nur zugegriffen werden muss, wenn es vorhanden ist, und es jederzeit von einer anderen Person gelöscht werden kann, wird std :: schwach_ptr verwendet, um das Objekt zu verfolgen, und es wird in std konvertiert: : shared_ptr, um temporären Besitz zu übernehmen. Wenn das ursprüngliche std :: shared_ptr zu diesem Zeitpunkt zerstört wird, verlängert sich die Lebensdauer des Objekts, bis auch das temporäre std :: shared_ptr zerstört wird.
Darüber hinaus wird std :: schwach_ptr verwendet, um Zirkelverweise von std :: shared_ptr zu brechen.
quelle
Der gemeinsame Zeiger hat einen Nachteil: shared_pointer kann die Eltern-Kind-Zyklusabhängigkeit nicht verarbeiten. Bedeutet, wenn die übergeordnete Klasse das Objekt der untergeordneten Klasse mithilfe eines gemeinsam genutzten Zeigers verwendet, in derselben Datei, wenn die untergeordnete Klasse das Objekt der übergeordneten Klasse verwendet. Der gemeinsam genutzte Zeiger kann nicht alle Objekte zerstören, selbst der gemeinsam genutzte Zeiger ruft den Destruktor im Zyklusabhängigkeitsszenario überhaupt nicht auf. Grundsätzlich unterstützt der gemeinsam genutzte Zeiger den Referenzzählmechanismus nicht.
Diesen Nachteil können wir mit schwachem Zeiger überwinden.
quelle
weak_ptr
Umgang mit einer zirkulären Abhängigkeit ohne Änderung der Programmlogik als Drop-In-Ersatz fürshared_ptr
?" :-)Wenn wir das Objekt nicht besitzen wollen:
Ex:
In der obigen Klasse besitzt wPtr1 nicht die Ressource, auf die wPtr1 zeigt. Wenn die Ressource gelöscht wurde, ist wPtr1 abgelaufen.
So vermeiden Sie zirkuläre Abhängigkeiten:
Wenn wir nun den shared_ptr der Klassen B und A erstellen, beträgt der use_count der beiden Zeiger zwei.
Wenn der shared_ptr den Gültigkeitsbereich verlässt, bleibt die Anzahl weiterhin 1, und daher werden die Objekte A und B nicht gelöscht.
Ausgabe:
Wie wir an der Ausgabe sehen können, werden A- und B-Zeiger niemals gelöscht und daher Speicher verloren.
Um solche Probleme zu vermeiden, verwenden Sie einfach schwaches_ptr in Klasse A anstelle von geteiltem_ptr, was sinnvoller ist.
quelle
Ich sehe
std::weak_ptr<T>
als Griff zu astd::shared_ptr<T>
: Es ermöglicht mir, das zu bekommen,std::shared_ptr<T>
wenn es noch existiert, aber es wird seine Lebensdauer nicht verlängern. Es gibt verschiedene Szenarien, in denen eine solche Sichtweise nützlich ist:Ein weiteres wichtiges Szenario besteht darin, Zyklen in Datenstrukturen zu unterbrechen.
Herb Sutter hat ein ausgezeichnetes Gespräch , in dem die beste Verwendung von Sprachfunktionen (in diesem Fall intelligente Zeiger) zur Gewährleistung der standardmäßigen Leckfreiheit erklärt wird (was bedeutet: Alles klickt durch die Konstruktion an Ort und Stelle; Sie können es kaum vermasseln). Es ist ein Muss.
quelle