Wann ist std :: schwach_ptr nützlich?


Antworten:


231

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.


8
Also kann std :: wake_ptr nur zeigen, wo ein anderer Zeiger zeigt, und es zeigt auf nullptr, wenn das Objekt, auf das gezeigt wird, gelöscht wird / nicht mehr von anderen Zeigern gezeigt wird?

27
@ RM: Grundsätzlich ja. Wenn Sie einen schwachen Zeiger haben, können Sie versuchen, ihn zu einem starken Zeiger zu machen. Wenn dieses Objekt noch vorhanden ist (weil noch mindestens ein starker Zeiger darauf vorhanden ist), ist diese Operation erfolgreich und Sie erhalten einen starken Zeiger darauf. Wenn dieses Objekt nicht vorhanden ist (weil alle starken Zeiger verschwunden sind), schlägt diese Operation fehl (und Sie reagieren normalerweise, indem Sie den schwachen Zeiger wegwerfen).
David Schwartz

12
Während ein starker Zeiger ein Objekt am Leben hält, kann ein schwaches_ptr es betrachten ... ohne die Lebensdauer des Objekts zu beeinträchtigen.
Die Vivandiere

3
Ein anderes Beispiel, das ich zumindest einige Male verwendet habe, ist die Implementierung von Beobachtern. Manchmal ist es praktisch, wenn das Subjekt eine Liste schwacher Zeiger verwaltet und eine eigene Listenbereinigung durchführt. Es spart ein wenig Mühe, Beobachter beim Löschen explizit zu entfernen, und vor allem müssen Sie keine Informationen zu den Themen zur Verfügung haben, wenn Sie Beobachter zerstören, was die Dinge im Allgemeinen erheblich vereinfacht.
Jason C

3
Warten Sie, was ist falsch daran, dass der Cache ein shared_ptr enthält und es einfach aus seiner Liste entfernt, wenn es aus dem Speicher gelöscht werden soll? Alle Benutzer haben trotzdem ein shared_ptr und die zwischengespeicherte Ressource wird gelöscht, sobald alle Benutzer damit fertig sind.
Rubenvb

299

std::weak_ptrist 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 sie std::shared_ptrdie Daten verwalten lassen und std::weak_ptrden Benutzern die Daten zur Verfügung stellen .expired()lock()

Sie können dies nicht std::shared_ptralleine tun , da alle std::shared_ptrInstanzen das Eigentum an den Daten teilen, die nicht entfernt werden, bevor alle Instanzen von std::shared_ptrentfernt wurden. Hier ist ein Beispiel für die Überprüfung auf baumelnden Zeiger mithilfe von 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 << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

1
Ok, es ist, als ob Sie lokal einen (besitzenden) Zeiger auf null setzen (Speicher löschen), alle anderen (schwachen) Zeiger auf denselben Speicher werden ebenfalls auf null gesetzt
Pat-Laugh

std::weak_ptr::lockErstellt eine neue std::shared_ptr, die das Eigentum an dem verwalteten Objekt teilt.
Sahib Yar

129

Eine andere Antwort, hoffentlich einfacher. (für andere Googler)

Angenommen, Sie haben Teamund MemberObjekte.

Offensichtlich ist es eine Beziehung: Das TeamObjekt hat Zeiger auf seine Members. Und es ist wahrscheinlich, dass die Mitglieder auch einen Rückzeiger auf ihr TeamObjekt 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 normalerweise shared_ptrund der "Eigentümer" verwendet a weak_ptrfür sein übergeordnetes Element und konvertiert es vorübergehend in, shared_ptrwenn er Zugriff auf sein übergeordnetes Element benötigt.

Speichern Sie einen schwachen ptr:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

Verwenden Sie es dann bei Bedarf

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

1
Wie ist das ein Speicherverlust? Wenn das Team zerstört wird, zerstört es seine Mitglieder, daher ist die Anzahl der gemeinsam genutzten_ptr-Refs 0 und auch zerstört?
Paulm

4
@paulm Team wird "seine" Mitglieder nicht zerstören. Der springende Punkt shared_ptrist, 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 werden shared_ptr.
Offirmo

1
Es wird sie nicht zerstören, aber sein shared_ptr wird damit den Gültigkeitsbereich verlassen, dekrementiert den use_count, also ist use_count an diesem Punkt 0 und der shared_ptr löscht, worauf es zeigt?
Paulm

2
@ Paulm Du hast recht. Aber da in diesem Beispiel das Team auch shared_ptrvon seinen "Teammitgliedern" referenziert wird, wann wird es zerstört? Was Sie beschreiben, ist ein Fall, in dem es keine Schleife gibt.
Offirmo

14
Es ist nicht so schlimm, würde ich denken. Wenn ein Mitglied zu vielen Teams gehören kann, funktioniert die Verwendung einer Referenz nicht.
Mazyod

22

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äuft std::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.


4
: Klingt nach einem guten Beispiel, aber können Sie Ihr Beispiel bitte etwas näher erläutern? Ich denke, wenn eine Aufgabe abgeschlossen ist, sollte sie bereits ohne regelmäßige Überprüfung aus dem std :: vector <std :: schwach_ptr <Aufgabe>> entfernt werden. Ich bin mir also nicht sicher, ob der std :: vector <std :: schwach_ptr <>> hier sehr hilfreich ist.
Gob00st

Ähnlicher Kommentar mit Warteschlangen: Angenommen, Sie haben Objekte und stellen sie für eine Ressource in die Warteschlange. Objekte können während des Wartens gelöscht werden. Wenn Sie also schwache_ptrs in die Warteschlange stellen, müssen Sie sich nicht darum kümmern, Einträge aus dieser Warteschlange zu löschen. Schwache_ptrs werden ungültig und dann verworfen, wenn sie entdeckt werden.
zzz777

1
@ zzz777: Die Logik, die die Objekte ungültig macht, ist sich möglicherweise nicht einmal der Existenz der Warteschlange oder des Vektors von Beobachtern bewusst. Der Beobachter führt also eine separate Schleife über die schwachen Zeiger durch, wirkt auf die noch lebenden und entfernt die toten aus dem Container ...
Kerrek SB

1
@KerekSB: Ja, und im Falle einer Warteschlange müssen Sie nicht einmal eine separate Schleife einrichten. Wenn eine Ressource verfügbar ist, verwerfen Sie abgelaufene schwache_Ptrs (falls vorhanden), bis Sie eine gültige (falls vorhanden) erhalten haben.
zzz777

Sie könnten auch die Threads selbst aus der Sammlung entfernen lassen, dies würde jedoch eine Abhängigkeit erzeugen und eine Sperrung erfordern.
Neugieriger

16

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_ptrObjekt mit std::bindoder Lambda-Captures in das asynchrone Handler-Objekt einzubinden .

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

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.


Warum hat es so lange this
gedauert

@Orwellophile behoben. Gewohnheitskraft bei Verwendung des self = shared_from_this()Idioms, wenn der Handler Methoden innerhalb derselben Klasse aufruft.
Emile Cormier

16

shared_ptr : enthält das reale Objekt.

schwach_ptr : Wird verwendet lock, um eine Verbindung zum tatsächlichen Eigentümer herzustellen, oder gibt shared_ptrandernfalls einen NULL- Wert zurück .

schwacher ptr

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.


14

weak_ptrEs ist auch gut, das korrekte Löschen eines Objekts zu überprüfen - insbesondere bei Komponententests. Ein typischer Anwendungsfall könnte folgendermaßen aussehen:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());

13

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:

  • Rohe Zeiger:
    • Roher Zeiger [dh SomeClass* ptrToSomeClass = new SomeClass();]
  • Intelligente Zeiger:
    • Einzigartige Zeiger [dh
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Geteilte Zeiger [dh
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Schwache Zeiger [dh
      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:

  • Sie sind beschäftigt und haben überlappende Besprechungen: Besprechung A und Besprechung B.
  • Sie beschließen, zu Besprechung A zu gehen, und Ihr Mitarbeiter geht zu Besprechung B.
  • Sie teilen Ihrem Kollegen mit, dass Sie beitreten werden, wenn Meeting B nach dem Ende von Meeting A noch läuft
  • Die folgenden zwei Szenarien könnten sich abspielen:
    • Meeting A endet und Meeting B läuft noch, also treten Sie bei
    • Meeting A endet und Meeting B wurde ebenfalls beendet, sodass Sie nicht teilnehmen können

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.


6

Abgesehen von den anderen bereits erwähnten gültigen Anwendungsfällen std::weak_ptrist ein großartiges Tool in einer Multithread-Umgebung, weil

  • Es besitzt das Objekt nicht und kann daher das Löschen in einem anderen Thread nicht behindern
  • std::shared_ptrin Verbindung mit std::weak_ptrist sicher gegen baumelnde Zeiger - im Gegensatz zu std::unique_ptrin Verbindung mit rohen Zeigern
  • std::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)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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_imageDatasFeld ä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 imagesToLoadwä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 Verwendung std::shared_ptrin Verbindung mit std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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.


2

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.


" Zirkelverweise brechen " wie?
Neugieriger

2

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.


Wie kann eine schwache Referenz mit einer zirkulären Abhängigkeit umgehen?
Neugieriger

1
@curiousguy, ein Kind verwendet einen schwachen Verweis auf den Elternteil, dann kann der Elternteil freigegeben werden, wenn keine gemeinsamen (starken) Verweise darauf verweisen. Wenn Sie also über das Kind auf den Elternteil zugreifen, muss die schwache Referenz getestet werden, um festzustellen, ob der Elternteil noch verfügbar ist. Um diese zusätzliche Bedingung zu vermeiden, kann alternativ ein Mechanismus zur Verfolgung von Zirkelreferenzen (entweder Markierungs-Sweep oder Prüfung von Nachzählungsabnahmen, die beide eine schlechte asymptotische Leistung aufweisen) die gemeinsamen Zirkelreferenzen unterbrechen, wenn die einzigen gemeinsamen Verweise auf Eltern und Kind von jedem stammen andere.
Shelby Moore III

@ShelbyMooreIII " muss getestet werden, um festzustellen , ob der Elternteil noch verfügbar ist " Ja, und Sie müssen in der Lage sein, korrekt auf den nicht verfügbaren Fall zu reagieren! Was bei einem echten (dh starken) Schiedsrichter nicht vorkommt. Was bedeutet, dass ein schwacher Ref kein Ersatzverlust ist: Er erfordert eine Änderung der Logik.
Neugieriger

2
@curiousguy Sie haben nicht gefragt: "Wie kann ein weak_ptrUmgang mit einer zirkulären Abhängigkeit ohne Änderung der Programmlogik als Drop-In-Ersatz für shared_ptr?" :-)
Shelby Moore III

2

Wenn wir das Objekt nicht besitzen wollen:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

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:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

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.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

Ausgabe:

A()
B()

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.


2

Ich sehe std::weak_ptr<T>als Griff zu a std::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:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Ein weiteres wichtiges Szenario besteht darin, Zyklen in Datenstrukturen zu unterbrechen.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

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.

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.