Ich habe angefangen, Smart Pointer von C++11 zu studieren und ich sehe keine nützliche Verwendung von std::weak_ptr
. Kann mir jemand sagen, wann std::weak_ptr
nützlich/notwendig ist?
Antworten
Zu viele Anzeigen?std::weak_ptr
ist ein sehr guter Weg zur Lösung des Schleppzeiger Problem. Bei der Verwendung von reinen Zeigern ist es unmöglich zu wissen, ob die referenzierten Daten freigegeben wurden oder nicht. Stattdessen kann man durch die Verwendung eines std::shared_ptr
die Verwaltung der Daten und die Bereitstellung std::weak_ptr
für die Nutzer der Daten, können diese die Gültigkeit der Daten durch den Aufruf expired()
o lock()
.
Dies ist nicht möglich mit std::shared_ptr
allein, denn alle std::shared_ptr
Instanzen teilen sich das Eigentum an den Daten, die nicht entfernt werden, bevor alle Instanzen von std::shared_ptr
entfernt werden. Hier ist ein Beispiel, wie man auf baumelnde Zeiger prüfen kann, indem man lock()
:
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << "weak1 value is " << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << "weak2 value is " << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
Ausgabe
weak1 is expired
weak2 value is 5
Ein gutes Beispiel wäre ein Cache.
Bei Objekten, auf die kürzlich zugegriffen wurde, möchten Sie sie im Speicher behalten, so dass Sie einen starken Zeiger auf sie halten. In regelmäßigen Abständen überprüfen Sie den Cache und entscheiden, auf welche Objekte in letzter Zeit nicht zugegriffen wurde. Diese brauchen Sie nicht im Speicher zu behalten, also entfernen Sie den Strong Pointer.
Was aber, wenn dieses Objekt in Gebrauch ist und ein anderer Code einen starken Zeiger darauf hält? Wenn der Cache seinen einzigen Zeiger auf das Objekt loswird, kann er es nie wieder finden. Der Cache behält also einen schwachen Zeiger auf Objekte, die er finden muss, wenn sie zufällig im Speicher bleiben.
Dies ist genau das, was ein schwacher Zeiger tut - es ermöglicht Ihnen, ein Objekt zu lokalisieren, wenn es noch da ist, aber nicht halten es um, wenn nichts anderes braucht es.
Eine andere, hoffentlich einfachere Antwort. (für Mit-Googler)
Angenommen, Sie haben Team
y Member
Objekte.
Offensichtlich ist es eine Beziehung: die Team
Objekt hat Zeiger auf seine Members
. Und es ist wahrscheinlich, dass die Mitglieder auch einen Rückverweis auf ihre Team
Objekt.
Dann hat man einen Abhängigkeitszyklus. Wenn Sie shared_ptr
werden Objekte nicht mehr automatisch freigegeben, wenn Sie die Referenz auf sie aufgeben, da sie sich gegenseitig zyklisch referenzieren. Dies ist ein Speicherleck.
Sie brechen dies, indem Sie weak_ptr
. Der "Eigentümer" verwendet in der Regel shared_ptr
und die "Besitzenden" verwenden eine weak_ptr
zu seinem Elternteil und konvertieren es vorübergehend à shared_ptr
wenn es Zugang zu seinem Elternteil benötigt.
Einen schwachen ptr speichern:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
dann bei Bedarf verwenden
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Hier ein Beispiel, das mir von @jleahy gegeben wurde: Angenommen, Sie haben eine Sammlung von Aufgaben, die asynchron ausgeführt und von einem std::shared_ptr<Task>
. Möglicherweise möchten Sie mit diesen Aufgaben in regelmäßigen Abständen etwas tun, so dass ein Timer-Ereignis eine std::vector<std::weak_ptr<Task>>
und den Aufgaben etwas zu tun geben. Gleichzeitig kann aber auch eine Aufgabe beschlossen haben, dass sie nicht mehr gebraucht wird und sterben. Der Zeitgeber kann daher überprüfen, ob die Aufgabe noch am Leben ist, indem er aus dem schwachen Zeiger einen gemeinsamen Zeiger erstellt und diesen gemeinsamen Zeiger verwendet, sofern er nicht null ist.
Bei der Verwendung von Zeigern ist es wichtig, die verschiedenen Arten von Zeigern zu kennen und zu wissen, wann es sinnvoll ist, die einzelnen Typen zu verwenden. Es gibt vier Arten von Zeigern, die in zwei Kategorien unterteilt sind:
- Rohe Zeiger:
- Rohzeiger [ d.h.
SomeClass* ptrToSomeClass = new SomeClass();
]
- Rohzeiger [ d.h.
- Intelligente Hinweisschilder:
- Eindeutige Zeiger [ d.h.
std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
] - Gemeinsam genutzte Zeiger [ d.h.
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
] - Schwache Zeiger [ d.h.
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
- Eindeutige Zeiger [ d.h.
Rohe Zeiger (manchmal auch als "Legacy-Zeiger" oder "C-Zeiger" bezeichnet) bieten ein "nacktes" Zeigerverhalten und sind eine häufige Quelle von Fehlern und Speicherlecks. Rohe Zeiger bieten keine Möglichkeit, die Eigentümerschaft der Ressource zu verfolgen, und die Entwickler müssen "delete" manuell aufrufen, um sicherzustellen, dass sie kein Speicherleck verursachen. Dies wird schwierig, wenn die Ressource gemeinsam genutzt wird, da es schwierig sein kann, herauszufinden, ob irgendwelche Objekte noch auf die Ressource zeigen. Aus diesen Gründen sollten rohe Zeiger im Allgemeinen vermieden und nur in leistungskritischen Abschnitten des Codes mit begrenztem Umfang verwendet werden.
Eindeutige Zeiger sind ein grundlegender intelligenter Zeiger, der den zugrundeliegenden Rohzeiger auf die Ressource "besitzt" und für den Aufruf von delete und die Freigabe des zugewiesenen Speichers verantwortlich ist, sobald das Objekt, das den eindeutigen Zeiger "besitzt", den Anwendungsbereich verlässt. Der Name "unique" bezieht sich auf die Tatsache, dass nur ein Objekt den unique pointer zu einem bestimmten Zeitpunkt "besitzen" kann. Der Besitz kann mit dem Befehl move auf ein anderes Objekt übertragen werden, aber ein eindeutiger Zeiger kann niemals kopiert oder gemeinsam genutzt werden. Aus diesen Gründen sind eindeutige Zeiger eine gute Alternative zu rohen Zeigern, wenn nur ein Objekt den Zeiger zu einem bestimmten Zeitpunkt benötigt, was den Entwickler von der Notwendigkeit entbindet, am Ende des Lebenszyklus des besitzenden Objekts Speicher freizugeben.
Gemeinsame Zeiger sind eine weitere Art von intelligenten Zeigern, die eindeutigen Zeigern ähneln, aber es vielen Objekten ermöglichen, Eigentümer des gemeinsamen Zeigers zu sein. Wie eindeutige Zeiger sind gemeinsam genutzte Zeiger dafür verantwortlich, den zugewiesenen Speicher freizugeben, sobald alle Objekte auf die Ressource verweisen. Dies wird durch eine 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 Bereich verlässt oder nicht mehr auf die Ressource verweist, wird der Referenzzähler um eins verringert. Wenn der Referenzzähler Null erreicht, wird der zugewiesene Speicher freigegeben. Aus diesen Gründen sind gemeinsame Zeiger eine sehr leistungsfähige Art von intelligenten Zeigern, die immer dann verwendet werden sollten, wenn mehrere Objekte auf dieselbe Ressource zeigen müssen.
Schwache Zeiger schließlich sind eine weitere Art von intelligenten Zeigern, die nicht direkt auf eine Ressource verweisen, sondern auf einen anderen Zeiger (schwach oder gemeinsam). Weak-Pointer können nicht direkt auf ein Objekt zugreifen, aber sie können feststellen, ob das Objekt noch existiert oder ob es abgelaufen ist. Ein "weak pointer" kann vorübergehend in einen "shared pointer" umgewandelt werden, um auf das Objekt zuzugreifen, auf das er zeigt (sofern es noch existiert). Betrachten Sie zur Veranschaulichung das folgende Beispiel:
- Sie haben viel zu tun und es kommt zu Überschneidungen von Terminen: Besprechung A und Besprechung B
- Sie beschließen, zu Meeting A zu gehen und Ihr Kollege geht zu Meeting B
- Sie sagen Ihrem Kollegen, dass Sie an der Besprechung B teilnehmen werden, wenn diese nach dem Ende von Besprechung A noch andauert.
- Die folgenden zwei Szenarien könnten sich abspielen:
- Meeting A endet und Meeting B läuft noch, also nehmen Sie teil
- Besprechung A endet und Besprechung B ist ebenfalls beendet, so dass Sie nicht teilnehmen können.
In diesem Beispiel haben Sie einen schwachen Zeiger auf Besprechung B. Sie sind kein "Eigentümer" von Besprechung B, so dass diese ohne Sie beendet werden kann, und Sie wissen nicht, ob sie beendet wurde oder nicht, es sei denn, Sie überprüfen dies. Wenn die Besprechung noch nicht beendet ist, können Sie ihr beitreten und teilnehmen, andernfalls nicht. Dies ist etwas anderes als ein gemeinsamer Zeiger auf Besprechung B, da Sie dann sowohl in Besprechung A als auch in Besprechung B "Eigentümer" sind (und an beiden gleichzeitig teilnehmen).
Das Beispiel veranschaulicht, wie ein schwacher Zeiger funktioniert und wie nützlich er ist, wenn ein Objekt ein äußerer Zeiger sein muss Beobachter aber er möchte nicht die Verantwortung für das gemeinsame Eigentum tragen. Dies ist besonders dann nützlich, wenn zwei Objekte aufeinander verweisen müssen (auch bekannt als Zirkelreferenz). Bei gemeinsam genutzten Zeigern kann keines der beiden Objekte 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 hält, bei Bedarf immer noch auf das andere Objekt zugreifen, sofern es noch existiert.
- See previous answers
- Weitere Antworten anzeigen