Advanced  Services Sharedpointer Back Next Up Home

Sharedpointer

Im Gegensatz zu Autopointer können mehrere shared_ptr auf dasselbe Objekt zeigen. Alle diese Exemplare verfügen über einen internen Zähler, der die Anzahl der Referenzierungen speichert. Immer wenn ein Exemplar von shared_ptr einen Block verläßt wird dieser heruntergezählt. Der letzte verbleibende shared_ptr ruft dann den Destruktor.

Das folgende Beispiel erläutert das Vorgehen. Zuerst brauchen wir jedoch eine einfche Testklasse, um die Destruktoraufrufe verfolgen zui können.


Die Testklasse
class Test
{
public:
   int i;
   Test(int j = 0) : i(j) {}
   ~Test() { cout < "destructor test\n" < endl; }
};

shared_ptr erlaubt (wie auch unique_ptr) die Verwendung von selbst geschriebenen Deletern bzw. Allocatern. Im folgenden werden diese passend zur Testklasse vorgestellt.


Die Deleterfunktionen
void testdeleterfree(Test* t)
{
   std::cout << "testdeleterfree called" << std::endl;
   free(t);
};
void testdeleternew(Test* t)
{
   std::cout << "testdeleternew called" << std::endl;
   delete t;
};

Die Klassen für das Memorymanagement
// void operator() ist notwendig, in welcher Form auch immer
struct CustomDeleterClass
{
   void operator()(Test* pt)
   {
      cout << "custom deleter operator() in CustomDeleterClass" << endl;
      delete pt;
   }
};
struct CustomMemoryManager
{
   Test* alloc()
   {
      cout << "custom allocator in CustomMemoryManager" << endl;
      return new Test();
   }

   void operator()(Test* pt)
   {
      cout << "custom deleter operator() in CustomMemoryManager" << endl;
      delete pt;
   }
};

Benötigte includes.

#include <memory>   // und std:: falls der Compiler C++11 unterstützt
// ansonsten mit der boost library
#include <boost/tr1/memory.hpp>

Beispiel 1

Das Beispiel legt drei shared_ptr in zwei geschachtelten Blöcken an.

void std_shared_ptr_1()
{
   cout << "std::shared_ptr 1\n" << endl;
   std::shared_ptr<Test> sptr1( new Test(17) );
   cout << "sptr1.use_count = " << sptr1.use_count() << endl;  //
   // dereferenzieren möglich, da * und -> überladen
   cout << "sptr1->i = " << sptr1->i << endl;
   {
      cout << "   begin block" << endl;
      std::shared_ptr<Test> sptr2 = sptr1;
      cout << "   sptr2->i = " << sptr2->i << endl;
      std::shared_ptr<Test> sptr3 = sptr1;
      sptr3->i = 42;  //
      cout << "   sptr1->i = " << sptr1->i << endl;
      cout << "   sptr1.use_count = " << sptr1.use_count() << endl;  //
      cout << "   sptr2.use_count = " << sptr2.use_count() << endl;  //
      cout << "   sptr3.use_count = " << sptr3.use_count() << endl;  //
      cout << "   end block" << endl;
   }
   cout << "sptr1.use_count = " << sptr1.use_count() << endl;  //
   cout << "end std::shared_ptr 1" << endl;
}

Die Ausgabe

shared-pointer-1.jpg


Einige Memberfunktionen

Wie man im oberen Beispiel sieht, sind operator* und operator-> überladen, auch gibt es eine Methode T * get() const die den originalen raw-Pointer zurückliefert. Diese drei Funktionen finden sich bei allen Smartpointern außer bei weak_ptr.


swap(), reset()

swap() vertauscht den Inhalt zweier shared_ptr. Das folgende Beispiel zeigt dies.

void std_shared_ptr_2()
{
   cout << "std::shared_ptr 2\n" << endl;

   std::shared_ptr<Test> ptr1( new Test(17) );
   std::shared_ptr<Test> ptr2( new Test(42) );
   std::shared_ptr<Test> ptr3(ptr2);

   cout << "ptr1->i = " << ptr1->i << " : use_count = " << ptr1.use_count() << endl;
   cout << "ptr2->i = " << ptr2->i << " : use_count = " << ptr2.use_count() << endl;
   cout << "ptr3->i = " << ptr3->i << " : use_count = " << ptr3.use_count() << endl;

   ptr2.swap(ptr1); cout << "\nswap: ptr2 <--> ptr1\n" << endl;

   cout << "ptr1->i = " << ptr1->i << " : use_count = " << ptr1.use_count() << endl;
   cout << "ptr2->i = " << ptr2->i << " : use_count = " << ptr2.use_count() << endl;
   cout << "ptr3->i = " << ptr3->i << " : use_count = " << ptr3.use_count() << endl << endl;
}

Die Ausgabe

shared-pointer-2.jpg

Die Wirkung von reset() erkennt man am nächsten Beispiel.

void std_shared_ptr_3()
{
   cout << "std::shared_ptr 3\n" << endl;

   std::shared_ptr<Test> ptr( new Test(17) );
   cout << "ptr->i = " << ptr->i << " : use_count = " << ptr.use_count();
   cout << " address = " << ptr.get() << endl;
   cout << "reset intrusive_ptr" << endl;
   ptr.reset();
   cout << "address = " << ptr.get() << endl;
}

shared-pointer-3.jpg

Wie man der API entnehmen kann gibt es reset() in vier Varianten, da es möglich ist (wie bei unique_ptr) eigene Deleter und Allocator zu schreiben.


Verwendung der Deleterfunktionen

void std_shared_ptr_4()
{
   cout << "custom-deleters\n" << endl;

   // Bekanntgabe eines custom-deleters, hier std::free falls speicher mit malloc angeschafft wird
   std::shared_ptr<Test> sp1( (Test*)malloc(sizeof(Test)) , std::free );

   // custom-deleter der free() verwendet
   std::shared_ptr<Test> sp2( (Test*)malloc(sizeof(Test)) , testdeleterfree );

   // custom deleter der delete verwendet, delete ruft den Destruktor!
   std::shared_ptr<Test> sp3( new Test() , testdeleternew );
}

Die Ausgabe

shared-pointer-4.jpg


Verwendung der Deleterklasse und der MemoryManagementklasse

Die Ausgabe

void std_shared_ptr_5()
{
   cout << "custom-deleters 2\n" << endl;
   // variante 1
   CustomDeleterClass cdc;
   std::shared_ptr<Test> up1( new Test(), cdc);
   // variante 2
   CustomMemoryManager cmm;
   std::shared_ptr<Test> up2( cmm.alloc(), cmm );
}

shared-pointer-5.jpg


Falle 1

Die erste Falle ist leicht zu erkennen. Hier wurde eine Grundregel nicht befolgt und der Rawpointer noch einmal für eine zweite Initialisierung verwendet.

/*
   Was man nicht machen darf:
*/
void std_shared_ptr_6()
{
   cout << "std_shared_ptr 6\n" << endl;
   Test* ptest = new Test(17);   // gefährlich
   std::shared_ptr<Test> sptr1(ptest);
   cout << "sptr1.use_count = " << sptr1.use_count() << endl;  //
   // dereferenzieren möglich, da * und -> überladen
   cout << "sptr1->i = " << sptr1->i << endl;
   {
      cout << "   begin block" << endl;
      //lokal im Block
      std::shared_ptr<Test> sptr2(ptest);  // falsch 
      //std::shared_ptr<Test> sptr2 = sptr1;  // OK
      cout << "   sptr1.use_count = " << sptr1.use_count() << endl;  //
      cout << "   sptr2.use_count = " << sptr2.use_count() << endl;  //
      cout << "   end block" << endl;
   }
   cout << "sptr1.use_count = " << sptr1.use_count() << endl;  //
   cout << "end std_shared_ptr 6" << endl;
}

shared-pointer-6.jpg

Der Screenshot zeigt deutlich, daß hier der Destruktor zweimal aufgerufen wird.


Falle 2

Dieser zweite Fall ist deutlich raffinierter. Da der Fehler grundsätzlicher Natur kann er nur durch die Einführung eines anderen Konzepts behoben werden. weak-Pointer führen hier weiter. Das korrigierte Beispiel (Aufbrechen einer cyclic reference) findet man deshalb im Kapitel zu weak-Pointern. Wir entwerfen zwei Klassen, die jeweils einen shared-Pointer halten, der auf ein Element der anderen Klasse zeigen kann. Dadurch entsteht eine zyklische Struktur, mit der ein shared-Pointer nicht umgehen kann.

Zuerst brauchen wir zwei Klassen, der Einfachheit werden Strukturen verwendet.

//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;}
};

Es reichen nun ein paar Zeilen um das Problem auszulösen.

/*
   Speicher wird nicht freigegeben.
*/
void cyclicsharedpointer()
{
   cout << "cyclicsharedpointer" << endl; //

   shared_ptr<A> shptrA(new A);  // shptrA->b share_ptr ist mit nullptr initialisiert, use_count ist 0
   cout << "shptrA.use_count = " << shptrA.use_count() << endl;  // 1
   cout << "shptrA->b.use_count = " << shptrA->b.use_count() << endl;  // 0
   shptrA->b = make_shared<B>();  //
   cout << "shptrA->b.use_count = " << shptrA->b.use_count() << endl;  // 1
   shptrA->b->a = shptrA;
   cout << "shptrA.use_count = " << shptrA.use_count() << endl;  // 2
}

Der Screenshot zeigt deutlich, daß der Destruktor nicht aufgerufen wird.

shared-pointer-7.jpg

Durch die Anweisung in der Zeile mit roter Schrift wird der Kreis geschlossen. Fehlt diese Zeile, so wird der Speicher korrekt freigegeben.

shared-pointer-8.jpg












Valid XHTML 1.0 Strict top Back Next Up Home