Advanced  Services Weakpointer


weak_ptr

Ein weak_ptr ist eine Art abgespeckter shared_ptr und kann nur über einen shared_ptr oder weak_ptr initialisiert werden. Immer wenn man "ownership" nicht auf einen weiteren shared_ptr übertragen will oder kann, kann man einen weak_ptr einsetzen. Ein weak_ptr hat nur eingeschränkte Möglichkeiten, so ist weder operator* operator<< überladen, zudem gibt es keine get()-Methode, es gibt also keinen direkten Zugriff auf die Daten. Die einzige Möglichkeit ist das Erstellen eines neuen shared_ptr aus dem weak_ptr, z.Bsp mit der Methode weak_ptr.lock(). Das erste Beispiel zeigt die grundlegenden Eigenschaften eines weak_ptr.

Wir verwenden die folgende Testklasse.

class Test
{
public:
   int i;
   Test(int j = 0) : i(j)
   { cout << "Test() i = " << this->i << endl; }
   ~Test()
   { cout << "~Test() i = " << this->i << endl << endl; }
};

Einfache Eigensachaften

Das Programm

#include <memory>

/*
   Einfache Eigenschaften
*/
void weakpointer1()
{
   cout << "weakpointer \n" << endl;

   cout << "shared_ptr 1 anlegen" << endl;  //
   std::shared_ptr<Test> sptr1(new Test(17));
   cout << "weak_ptr anlegen" << endl;  //
   std::weak_ptr<Test> wptr(sptr1);
   //oder
   //wptr = sptr1;
   cout << "use_count = " << sptr1.use_count() << endl;  // 1
   // lock liefert einen shared_ptr und erhöht dadurch den Zähler
   cout << "shared_ptr 2 mit Hilfe des weak_ptr anlegen" << endl;  //
   auto sptr2 = wptr.lock();
   // oder
   //std::shared_ptr<Test> sptr2(wptr);
   cout << "use_count = " << wptr.use_count() << endl;  // 2
   cout << "reset des weak_ptr" << endl;  //
   wptr.reset();
   cout << "wptr: use_count = " << wptr.use_count() << endl;  // 0
   cout << "sptr1: use_count = " << sptr1.use_count() << endl;  // 2
   cout << "reset des shared_ptr 1" << endl;  //
   sptr1.reset();
   cout << "sptr1: use_count = " << sptr1.use_count() << endl;  // 0
   cout << "sptr2: use_count = " << sptr2.use_count() << endl;  // 1

   cout << "\nend weakpointer" << endl;
}

Die Ausgabe


Erkennen ungültiger weak_ptr

void weakpointer2()
{
   cout << "weakpointer \n" << endl;

   std::weak_ptr<Test> wptr;
   {
      std::shared_ptr<Test> sptr1;
      // usecount für beide 0
      {
         cout << "begin block" << endl;
         sptr1 = std::make_shared<Test>(42);
         cout << "use_count = " << sptr1.use_count() << endl;  // 1
         std::shared_ptr<Test> sptr2 = sptr1;   // use_count wird erhöht
         cout << "use_count = " << sptr1.use_count() << endl;  // 2
         wptr = sptr1;
         cout << "wptr.use_count  = " << wptr.use_count() << endl;  // 2  nicht erhöht
         cout << "end block" << endl;
      }
      cout << "wptr.use_count  = " << wptr.use_count() << endl;  // 1
   }
   cout << "wptr.use_count  = " << wptr.use_count() << endl;  //

   cout << "wptr " << (wptr.expired()? "is" : "is not") << " expired\n";
   //oder so
   try
   {
      std::shared_ptr<Test> sptr(wptr);
   }
   catch(bad_weak_ptr ex)
   {
      cout << ex.what() << " cannot create shared_ptr from weak_ptr wptr" << endl;
   }
   cout << "\nend weakpointer" << endl;
}

Die Ausgabe


Aufbrechen einer cyclic reference

Das letzte Beispiel der vorigen Seite behandelte den Fall einer zyklischen Referenz. Mit Hilfe eines weak_ptr können wir die zyklische Referenz aufbrechen. Dazu führen wir in der Klasse B einen weak_ptr ein.

Alte Fassung der beiden Klassen

//cyclic reference
struct B;  // forward declaration

struct A
{
   shared_ptr<B> b;
   ~A() {cout << "~A()" << endl; }
};

struct B
{
   shared_ptr<A> a;
   ~B() {cout << "~B()" << endl;}
};

Neue Fassung der beiden Klassen (die erste Klasse bleibt unverändert)

//weak cyclic
struct BB;  // forward declaration

struct AA
{
   shared_ptr<BB> bb;
   ~AA() {cout << "~AA()" << endl; }
};

struct BB
{
   weak_ptr<AA> aa;
   ~BB() {cout << "~BB()" << endl;}
};

Das Programm

/*
   Problem behoben : In BB ist nun ein weakpointer !
*/
void cyclicsharedpointer2()
{
   cout << "begin cyclicsharedpointer 2\n" << endl; //

   shared_ptr<AA> shptrAA(new AA);  // shptrAA->b share_ptr Initialisierung mit 0
   cout << "shptrAA.use_count = " << shptrAA.use_count() << endl;  // 1
   cout << "shptrAA->b.use_count = " << shptrAA->bb.use_count() << endl;  // 0
   shptrAA->bb = make_shared<BB>();  // zweiter shared_ptr;
   cout << "shptrAA->b.use_count = " << shptrAA->bb.use_count() << endl;  //
   shptrAA->bb->aa = shptrAA;
   cout << "shptrAA.use_count = " << shptrAA.use_count() << endl;  // 2

   cout << "\nend cyclicsharedpointer 2" << endl; //
}

Die Ausgabe


Aktivieren eines shared_ptrs über den weak_ptr

Im vorigen Beispiel fehlt noch die Verwendung des weak_ptr. Im nächsten Beispiel aktivieren wir über den weak_ptr einen shared_ptr und manipulieren die Daten des Objekts auf den der weak_ptr zeigt. Das geschieht problemlos in der Methode dosomething().

Die um einen Datenteil ergänzte Klasse AA

struct AA
{
   int i;
   shared_ptr<BB> bb;
   ~AA() {cout << "~AA()" << endl; }
};

Die um eine Methode erweiterte Klasse BB

struct BB
{
   weak_ptr<AA> aa;
   void dosomething()
   {
      cout << "dosomething" << endl;
      shared_ptr<AA> sha = aa.lock();
      sha->i = 42;
   }
   ~BB() {cout << "~BB()" << endl;}
};

Das Pprogramm

void cyclicsharedpointer3()
{
   cout << "begin cyclicsharedpointer 3\n" << endl; //

   shared_ptr<AA> shptrAA(new AA);  // shptrAA->b share_ptr Initialisierung mit 0
   cout << "shptrAA.use_count = " << shptrAA.use_count() << endl;  // 1
   cout << "shptrAA->b.use_count = " << shptrAA->bb.use_count() << endl;  // 0
   shptrAA->bb = make_shared<BB>();  // zweiter shared_ptr;
   cout << "shptrAA->b.use_count = " << shptrAA->bb.use_count() << endl;  // 1
   shptrAA->bb->aa = shptrAA;
   cout << "shptrAA.use_count = " << shptrAA.use_count() << endl;  // 2
   // Verwenden des weak_ptr als shred_ptr
   (shptrAA->bb)->dosomething();
   cout << "shptrAA->i = " << shptrAA->i << endl;

   cout << "\nend cyclicsharedpointer 3" << endl; //
}

Die Ausgabe