std::weak_ptr

Inhaltsverzeichnis[Anzeigen]

std::unique_ptr verkörpert das Konzept des exklusiven, std::shared_ptr des geteilten Besitzes. Bleibe ich in diesem Bild, dann verkörpert der std::weak_ptr das Konzept des zeitlich eingeschränkten Besitzes, den er leiht sich die Ressource von einem std::shared_ptr aus. Der std::weak_ptr besitzt insbesondere eine Existenzberechtigung: Zyklische Referenzen von std::shared_ptr zu brechen.

Betrachte ich die Interface des Smart Pointers std::weak_ptr, dann ist dieses gar nicht so smart, sondern sehr eingeschränkt.

Das Interface

std::weak_ptr verändert im Gegensatz zu std::shared_ptr nicht den Referenzzähler auf die geteilte Ressource. Das zeigt das Beispiel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// weakPtr.cpp

#include <iostream>
#include <memory>

int main(){

  std::cout << std::boolalpha << std::endl;

  auto sharedPtr=std::make_shared<int>(2011);
  std::weak_ptr<int> weakPtr(sharedPtr);
  
  std::cout << "weakPtr.use_count(): " << weakPtr.use_count() << std::endl;
  std::cout << "sharedPtr.use_count(): " << sharedPtr.use_count() << std::endl;
  std::cout << "weakPtr.expired(): " << weakPtr.expired() << std::endl;

  if( std::shared_ptr<int> sharedPtr1 = weakPtr.lock() ) {
    std::cout << "*sharedPtr: " << *sharedPtr << std::endl;
    std::cout << "sharedPtr1.use_count(): " << sharedPtr1.use_count() << std::endl;
  }
  else{
    std::cout << "Don't get the resource!" << std::endl;
  }

  weakPtr.reset();
  if( std::shared_ptr<int> sharedPtr1 = weakPtr.lock() ) {
    std::cout << "*sharedPtr: " << *sharedPtr << std::endl;
    std::cout << "sharedPtr1.use_count(): " << sharedPtr1.use_count() << std::endl;
  }
  else{
    std::cout << "Don't get the resource!" << std::endl;
  }

  std::cout << std::endl;

}

 

In Zeile 11 erzeuge ich einen std::weak_ptr, der sich eine Ressource von dem std::shared_ptr ausleiht. Die Ausgabe des Programms zeigt, das der Referenzzähler auf die Ressource jeweils 1 ist (Zeile 13 und 14). Das heißt insbesondere, dass der std::weak_ptr den Referenzzähler nicht erhöht. Der Aufruf weakPtr.expired() gibt zurück, ob die Ressource bereits gelöscht wurde. Das ist äquivalent zu weakPtr.use_count() == 0. Verweist der std::weak_ptr weakPtr temporär auf eine Ressource, so lässt sich durch den Aufruf weakPtr.lock() in Zeile 17 ein std::shared_ptr erzeugen. Damit wird natürlich auch der Referenzzähler in Zeile 18 auf 2 erhöht. Setze ich den weakPtr (Zeile 25) zurück, so besitzt er keine Ressource mehr und der Aufruf weakPtr.lock() schlägt fehl.

weakPtr

Das war fast schon die ganze Geschichte zum std::weak_ptr. Aber nur fast, denn der std::weak_ptr besitzt ein klassisches Einsatzgebiet. Er hilft zyklische Referenzen von std::shared_ptr zu brechen.

Zyklische Referenzen

Zyklische Referenzen von std::shared_ptr entstehen genau dann, wenn std::shared_ptr gegenseitig auf sich verweisen.

Das Problem

Besteht eine zyklische Referenz von std::shared_ptr, so erreicht der Referenzzähler nie den Wert 0. Damit wird die Ressource nicht automatisch gelöscht. Dies ist aber genau die Existenzberechtigung des std::shared_ptr. Beispiel gefällig? 

In der Graphik befinden sich zwei Zyklen. Zum einen zwischen der Mutter und ihrer Tocher, zum anderen zwischen der Mutter und ihrem Sohn. Der feine Unterschied ist aber, das die Mutter ihre Tochter mit einem std::weak_ptr referenziert. Damit ist shared_ptr-Zyklus aufgebrochen.

cycle

Wer mehr am Sourcecode orientiert ist, der findet die gleiche Struktur hier nochmals wieder.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// cycle.cpp

#include <iostream>
#include <memory>

struct Son;
struct Daughter;

struct Mother{
  ~Mother(){
    std::cout << "Mother gone" << std::endl;
  }
  void setSon(const std::shared_ptr<Son> s ){
    mySon=s;
  }
  void setDaughter(const std::shared_ptr<Daughter> d ){
    myDaughter=d;
  }
  std::shared_ptr<const Son> mySon;
  std::weak_ptr<const Daughter> myDaughter;
};

struct Son{
  Son(std::shared_ptr<Mother> m):myMother(m){}
  ~Son(){
    std::cout << "Son gone" << std::endl;
  }
  std::shared_ptr<const Mother> myMother;
};

struct Daughter{
  Daughter(std::shared_ptr<Mother> m):myMother(m){}
  ~Daughter(){
    std::cout << "Daughter gone" << std::endl;
  }
  std::shared_ptr<const Mother> myMother;
};

int main(){
  std::cout << std::endl;
  {
    std::shared_ptr<Mother> mother= std::shared_ptr<Mother>( new Mother);
    std::shared_ptr<Son> son= std::shared_ptr<Son>( new Son(mother) );
    std::shared_ptr<Daughter> daughter= std::shared_ptr<Daughter>( new Daughter(mother) );
    mother->setSon(son);
    mother->setDaughter(daughter);
  }
  std::cout << std::endl;
}

 

Durch den künstlichen Block in Zeile 41 bis 47 wird die Lebenszeit von mother, son und daughter eingeschränkt. Oder anders ausgedrückt. mother, son und daughter verlieren ihre Gültigkeit und damit sollter der Konstruktor der Klasse Mother (Zeile 10 - 12), Son (Zeile 25 - 27) und Daughter (Zeile 33 - 35) automatisch aufgerufen werden.

Tatsächlich wird aber nur der Destruktor der Klasse Daughter aufgerufen.

cycle

Die Graphik oder auch der Source bringen es auf den Punkt. Zwischen mother und son besteht eine zyklische Referenz von std::shared_ptr.  Damit ist Referenzzähler immer größer als 0 und der Destruktor der Ressourcen kann nicht aufgerufen werden. Das gilt aber nicht für mother und daughter. Verlässt daughter ihren Gültigkeitsbereich, führt dies dazu, dass der Referenzzähler von std::shared_ptr myMother (Zeile 36) den Wert 0 erhält und automatisch die Ressource freigegeben wird.

Wie geht's weiter?

Die Container der Standard Template Library (STL) legen automatische dynamischen Speicher für ihre Elemente an. Diese trifft auf die assoziativen und die sequentiellen Container zu. Besonders mächtig ist das automatische Speichermanagement der sequentiellen Container std::vector und des std::string. Auch wenn std::string kein sequentieller Container der STL ist, ist er einem std::vector<char> sehr ähnlich. Grund genug, sich das automatische Speichermanagement der beiden Container im nächsten Artikel genauer anzuschauen.

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 

 

 

 

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare