Cpp 11 SmartPointer

Eines meiner, und wahrscheinlich vieler, vieler anderer C++ Entwickler, Lieblings-‘Features’ des neunen C++11 Standard sind die sogenannten SmartPointer.

“In der Informatik ist ein SmartPointer ein abstrakter Datentyp, der einen Zeiger simuliert und noch zusätzliche Funktionen bietet, wie z.B. automatische Speicherverwaltung. Diese zusätzlichen Funktionen sollen Bugs durch fehlerhafte Verwendung von Pointern reduzieren unter Beibehaltung der Effizienz. In der Regel tracken Smart Pointers auf den Speicher auf welchen sie verweisen.

Fehlerhafte Verwendung von Pointern ist einer der häufigsten Gründe für Bugs/Speicherlecks im Programm. SmartPointer verhindern das Auftreten von sog. Speicherlecks, indem sie die Ressourcen-Freigabe automatisieren. Die Ressource eines SmartPointers wird automatisch freigegeben, wenn der letzte (oder einzige) Owner der Ressource zerstört wurde, beispielsweise beim Verlassen des Scopes. Zusätzlich werden hängende Pointer durch den Gebrauch von SmartPointern eliminiert, da diese automatisch die Ressource freigeben.

Es gibt verschiedene Arten von SmartPointern. Einige arbeiten mit einem Reference-Count, andere indem sie den Besitz einer Ressource über einen einzigen Pointer für sich fest beanspruchen. In Managed Sprachen (C#, Java, etc.) sind Pointer als natürlich auch SmartPointer überflüssig, da hier die Garbage Collection automatisch durchgeführt wird.”

Quelle: Wikipedia

Sämtliche dieser neuen SmartPointer befinden sich in dem <memory.h> header file und können dadurch verwendet werden.

Im Grunde gibt es 3 Arten dieser Smart Pointer:

Unique Pointer

Shared Pointer

Weak Pointer

Jeder von diesen hat besondere Eigenschaften. Das macht diesen somit für bestimmte Anwendungsfälle so gut wie unverzichtbar. Dadurch, dass durch diese Smartpointer Speicherlecks verhindert und Danglingpointer eliminiert werden, gibt es keinerlei Gründe dafür, diese neue Funktionalität nicht bis zum Erbrechen zu verwenden. Dadurch, dass hierbei Templates zum Einsatz kommen, sind die Smartpointer generisch und können somit auf x-beliebige Ressourcen verweisen. Außerdem gibt es einen neuen Initialisierungswert für Pointer ‘nullptr’.

Wie bisher auch kann man mit einer einfachen If-Abfrage überprüfen, ob die Speicherallokierung für den jeweiligen Pointer erfolgreich war oder fehlgeschlagen ist. 

  1: if (p2) // True wenn die Speicherallokierung für p2 erfolgreich war
  2: {
  3:     // Hier könnte ihr Code zu p2 stehen!
  4: }

Eigenschaften

Jeder dieser 3 Smart Pointer hat im Grunde die selben Eigenschaften, welche den Zugriff und die Verwaltung der dahinterliegenden Ressource verwalten.

Get

Liefert die Speicheradresse der Ressource zurück.

  1: std::unique_ptr<int> p1(new int(5));
  2: cout << *p1.get() << "\n";

Reset

Löscht den Speicher und setzt den Pointer wieder auf NULL zurück.
Sollte noch ein anderer Pointer auf die Ressource verweisen, wird je nachdem welchen Smartpointer man verwendet hat, entweder die Ressource komplett freigegeben oder lediglich der Pointer auf NULL gesetzt.

  1: //p1 = double(5) -> p2 = p1
  2: p2.reset(); //Deletes the memory
  3: p1.reset(); //Depends on Pointer-Type

Count

Durch den sogenannten Referencecount werden Speicherlecks & Danglingpointer eliminiert. Jeder Smartpointer hat eine Count-Eigenschaft, bei welcher man die Anzahl an Pointern bekommt, welche auf die jeweilige Ressource verweisen.
Sobald der Referencecount den Wert 0 erreicht, wird die Ressource automatisch freigegeben.

  1: cout << p1.use_count() << "\n";

Swap

Diese Methode ermöglicht das ‘Weitergeben’ der Ressource an einen anderen Smart Pointer.
Hierbei kann genauso gut mit der Move-Semantik gearbeitet werden.

  1: p1.swap(p3);

Unique Pointer

Der Unique Pointer ist, wie der Name schon sagt, ein Pointer, welcher eine Ressource für sich alleine beansprucht. Sobald nun der Uniqueptr den Scope verlässt, wird automatisch die Ressource auf welche dieser verweist, freigegeben und somit Speicherlecks vermieden.
Falls man mit Swap oder Move die Ressource an einen anderen Uniquepointer weitergeben sollte, wird der Ursprungspointer auf NULL zurückgesetzt und der neue Pointer bekommt den Verweis auf die Ressource zugewiesen. Somit wird der Referencecount von 1 nicht überschritten.

  1: std::unique_ptr<int> p1(new int(5));
  2: cout << *p1.get() << "\n";
  3: //std::unique_ptr<int> p2 = p1; //Compile error. (UniquePtr max RefCount = 1)
  4: std::unique_ptr<int> p3 = std::move(p1); //Übergibt den Besitz der Ressource von p1 an p3 weiter. p1 wird zurückgesetzt.
  5: //oder
  6: p1.swap(p3);
  7: //cout << *p1.get() << "\n";    //Runtime-Exception (Nullreference)
  8: cout << *p3.get() << "\n";
  9: p3.reset(); //gibt die Ressource frei
 10: p1.reset(); //Keine Operation wird durchgeführt -> RefCount von p1 = 0 somit NULL

 

Shared Pointer

Sind den bisherigen ‘Stupid’ Pointer am ähnlichsten. Mehrere Pointer können auf die Ressource verweisen, jedoch werden auch hier Speicherlecks und Dangling-Pointer durch die selbstständige ‘Garbage Collection’ eliminiert.
Anhand des ReferenceCounts kann man jederzeit abfragen, wie viele Pointer derzeit auf die Ressource verweisen. Falls dieser den Wert 0 erreichen sollte, wird die Ressource automatisch freigegeben und somit Speicherlecks vermieden.

  1: std::shared_ptr<int> p1(new int(5));
  2: std::shared_ptr<int> p2 = p1; //Beide verweisen auf den selben Speicher
  3: cout << p1.use_count() << "\n"; // -> 2
  4: 
  5: p1.reset(); //Speicher exisitert noch -> Refcount 1
  6: p2.reset(); //Speicher wird freigegeben -> Refcount 0

Weak Pointer

Weak Pointer sind sog. schwache Referenzen (uncounted) auf einen bereits existierenden SharedPtr.

Diese schwachen Pointer sind keineswegs schwach oder unnütz, jedoch haben sie nur eine ganz spezifische Aufgabe: das Weitergeben von Ressourcen ohne den ReferenceCount zu erhöhen. Falls nun bis zum Weitergeben der Ressource der Count 0 erreichen sollte, wird diese freigegeben und somit auch der Weak Pointer nutzlos. Mit der Lock()-Methode kann die Ressource, auf welche der Weak Pointer verweist, an andere Shared Pointer weitergeben, falls diese noch nicht freigegeben wurde.

 

  1: std::shared_ptr<int> p1(new int(5));
  2: std::weak_ptr<int> wp1 = p1; //p1 immer noch alleiniger 'Besitzer' des Speichers
  3: 
  4: {
  5:     std::shared_ptr<int> p2 = wp1.lock(); //P1 & P2 besitzen den Speicher
  6:     if (p2) // Da P2 über einen WeakPointer initialisert wurde, sollte stets überprüft werden ob die Ressource noch existiert
  7:     {
  8:         //Hier könnte ihr Code zu p2 stehen!
  9:     }
 10: } //p2 wird freigegeben (Out of scope) -> p1 alleiniger Besitzer
 11: 
 12: p1.reset(); //Speicher wird freigegeben weil RefCount == 0
 13: 
 14: std::shared_ptr<int> p3 = wp1.lock(); //Speicher ist nicht mehr vorhanden, also bekommen wir einen leeren SharedPtr
 15: if (p3)
 16: {
 17:     //Wird nicht ausgeführt
 18: }
Kommentare sind geschlossen