[X] Schlaue Zeiger - Boost Smart Pointer



  • Inhalt

    Smart Pointer können komplexe Anwendungen in C++ wesentlich vereinfachen. Sie bieten hauptsächlich eine automatische Speicherverwaltung ähnlich derer in restriktiveren Sprachen (z.B. VB, C#), sind aber auch in anderen Situationen hilfreich.

    _________________________________________________________________________________

    Was sind Smart Pointer?

    Ein Smart Pointer verhält sich wie ein Zeiger, gibt das referenzierte Objekt aber automatisch frei, wenn es nicht mehr benötigt wird.

    "Nicht mehr benötigt" lässt sich in C++ schwer definieren. Daher verwendet man verschiedene Smart Pointer-Implementationen, die auf die häufigsten Szenarien angepasst sind. Neben dem automatischen Freigeben sind natürlich noch andere Anwendungen möglich.

    Smart Pointer-Implementationen findet man in vielen Bibliotheken - jede mit ihren eigenen Vorzügen und Problemen. Dieser Artikel bezieht sich auf die Smart Pointer der Boost-Bibliotheken, einer hochwertigen OpenSource-Bibliothekssammlung, von denen einige für die Einbindung in den nächsten C++-Standard vorgesehen sind.

    Boost bietet die folgenden Implementationen:

    • shared_ptr<T>
      Zeiger auf T. Ein Referenzzähler bestimmt, wann das Objekt gelöscht werden muss
      shared_ptr ist der flexibelste Smart Pointer aus boost.
    • scoped_ptr<T>
      Zeiger auf T, das referenzierte Objekt wird gelöscht wenn der scoped_ptr den Gültigkeitsbereich verlässt.
      Gleiche Performance wie T *. Keine Zuweisung an andere scoped_ptr-Instanz möglich.
    • intrusive_ptr<T>
      Ein weiterer referenzgezählter Zeiger. Bessere Performance als shared_ptr, aber die Referenzzählung muss separat implementiert sein. Damit lassen sich aber auch "fremde" Referenzzähler (z.B. COM) unterbringen.
    • weak_ptr<T>
      Ein schwacher Zeiger, zusammen mit shared_ptr zum Aufbrechen zirkulärer Referenzen
    • shared_array<T>
      wie shared_ptr, aber Zugriff ist wie auf Array von T
    • scoped_array<T>
      wie scoped_ptr, aber Zugriff ist wie auf Array von T

    _________________________________________________________________________________

    Ganz einfach: boost::scoped_ptr<T>

    scoped_ptr ist der einfachste Boost Smart Pointer. Er garantiert automatisches Löschen, wenn der Zeiger den Gültigkeitsbereich verlä***ss***t.

    ~Hinweis zum Beispielcode:
    Die Beispiele verwenden eine Hilfsklasse CSample, die für Konstruktion, Zuweisung usw. Meldungen ausgibt. Trotzdem ist es sicherlich interessant, die Beispiele unter dem Debugger nachzuvollziehen. Die Quellen enthalten die notwendigen [kor]Boost[/kor]-Header (bitte "Boost installieren" weiter unten beachten!)~

    Beispiel 1 - mit normalen Zeigern

    void Sample1_Plain()
    {
      CSample * pSample(new CSample);
    
      if (!pSample->Query() ) // irgendeine Funktion...
      {
        delete pSample;
        return;
      }
    
      pSample->Use();
      delete pSample;
    }
    

    Beispiel 2 - mit scoped_ptr<T>

    #include "boost/smart_ptr.h"
    
    void Sample1_ScopedPtr()
    {
      boost::scoped_ptr<CSample> samplePtr(new CSample);
    
      if (!samplePtr->Query() ) // irgendeine Funktion...
        return;    
    
      samplePtr->Use();
    }
    

    Einen normalen Zeiger muss man an allen Stellen freigeben, an denen man die Funktion verlässt. Das ist leicht zu übersehen, wenn man Exceptions verwendet. Verwendet man wie im zweiten Beispiel einen scoped_ptr, wird er automatisch freigegeben - auch wenn CSample::Query eine Exception wirft!

    Der Vorteil ist nicht zu übersehen: In einer komplexeren Funktion wird ein delete schnell vergessen - besonders, wenn man ein bisschen aufräumt.

    scoped_ptr eignet sich für: Automatische Freigabe von lokalen Objekten und Klassendaten, verzögerte Initialisierunng, Implementierung von PIMPL und RAII
    was geht nicht: Als Element in STL-Container, mehrere Zeiger auf das gleiche Objekt, andere Allokatoren als new/delete
    Performance: Praktisch identisch mit normalem Zeiger

    ~Tipp: Wer PIMPL (pointer to implementation, auch handle/body) und RAII (Resource Acquisition Is Initialization) nicht kennt, sollte sich ganz fix ein gutes C++-Buch [kor]zur Hand nehmen[/kor] und diese wichtigen Konzepte nachholen. Smart Pointer sind nur eine (bequeme) Möglichkeit, diese zu implementieren.~

    scoped_ptr verbietet direkte implizite Zuweisungen:

    scoped_ptr<CSample> ptrA(new CSample);
    scoped_ptr<CSample> ptrB = ptrA; // Compile-Fehler!  
    ptrA = new CSample;              // Compile-Fehler!
    

    Das vermeidet die üblichen Fehler im Umgang mit Smart Pointern, die meistens am Übergang zwischen Smart Pointer und normalem Zeiger auftreten. Explizit darf man natürlich alles (und ist selbst schuld, wenn es schief geht):

    T* scoped_ptr<T>::get()      // gibt den enthaltenen Zeiger zurück
    scoped_ptr<T>::reset(T *)    // ersetzt den enthaltenen Zeiger mit einer neuen Instanz. 
                                 // die vorige Instanz wird dabei freigegeben
    

    _________________________________________________________________________________

    Referenzzähler und shared_ptr<T>

    Wenn man mehrere Smart Pointer auf das gleiche Objekt zulassen möchte, benötigt man ein anderes Kriterium für "nicht mehr benötigt".
    Referenzgezählte Zeiger überwachen, wie viele Zeiger auf ein Objekt verweisen. Dazu wird jedem Objekt ein Zähler zugeordnet. Dieser wird bei einer Zeigerzuweisung erhöht, und im Zeigerdestruktor runtergezählt. Wenn der Zähler 0 erreicht, wird das Objekt gelöscht.

    Boost bietet shared_ptr als referenzgezählten Zeiger und "kleinen Alleskönner". Zuerst ein kleines Beispiel:

    void Sample2_Shared()
    {
      // (A) Erzeugen einer [kor]CSample-Instanz[/kor] mit einer Referenz
      boost::shared_ptr<CSample> p1(new CSample); 
      printf("The Sample now has %i references\n", p1.use_count()); // use_count() == 1
    
      // (B) An zweiten Zeiger zuweisen:
      boost::shared_ptr<CSample> p2 = mySample; 
      printf("The Sample now has %i references\n", p2.use_count()); // use_count() == 2
    
      // (C) den ersten Zeiger auf NULL setzen
      p1.reset(); 
      printf("The Sample now has %i references\n", p2.use_count());  // use_count() == 1
    
      // Das bei (A) allozierte Objekt wird freigegeben, wenn p2 den Gültigkeitsbereich verlässt
    }
    

    Zeile (A) erzeugt eine CSample-Instanz auf dem Heap und speichert einen Zeiger darauf in dem shared_ptr mySample. Das sieht dann so aus:

    [*** Bild 1 ***]

    In (B) wird ein zweiter Zeiger auf das Objekt angelegt:

    [*** Bild 2 ***]

    mySample wird mittels reset() auf NULL gesetzt (reset() ist identisch p=NULL für einen normalen Zeiger). CSample wird noch von p2 referenziert:

    [*** Bild 3 ***]

    Erst wenn die letzte Referenz auf CSample verschwindet, wird CSample gelöscht:

    [*** Bild 3 ***]

    Häufige Anwendungsfälle sind:

    • p1 und p2 in obigem Beispiel haben voneinander unabhängige Lebensdauer
    • Container von Zeigern (z.B. ein std::vector< boost::shared_ptr<MyUncopyableClass> >)
    • PIMPL + RAII für von mehreren unabhängigen Clients genutzten Ressourcen / Objekten

    _________________________________________________________________________________

    Beispiel für shared_ptr<T>: shared_ptr im STL-Container

    Viele Container-Klassen (u.a. STl-Container) erfordern Kopieroperationen, z.B. wenn ein existierendes Element in eine Liste oder einen Vektor eingefügt werden soll. Das ist für komplexe Klassen ungünstig und für manche Klassen sogar unmöglich. In diesem Fall weicht man üblicherweise auf einen Container von Zeigern aus:

    std::vector<CMyLargeClass *> vec;
    vec.push_back( new CMyLargeClass("dicke fette Zeichenkette") );
    

    Damit ist man aber als "Nutzer" wieder verantwortlich für die Speicherverwaltung. Mit einem shared_ptr wird es wieder einfacher:

    typedef boost::shared_ptr<CMyLargeClass>  CMyLargeClassPtr;
    std::vector<CMyLargeClassPtr> vec;
    vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
    

    Sehr ähnlich, aber nun werden die Container-Elemente wieder automatisch mit dem Container freigegeben - es sei denn, es gibt noch einen anderen shared_ptr auf ein Element, wie im Folgenden demonstriert:

    void Sample3_Container()
    {
      typedef boost::shared_ptr<CSample> CSamplePtr;
    
      // (A) Vektor mit CSample-Zeigern:
      std::vector<CSamplePtr> vec;
    
      // (B) drei Elemente hinzufügen
      vec.push_back(CSamplePtr(new CSample("A"));
      vec.push_back(CSamplePtr(new CSample("B"));
      vec.push_back(CSamplePtr(new CSample("C"));
    
      // (C) [kor]Einen Zeiger auf dem zweiten[/kor] Element "behalten": 
      CSamplePtr anElement = vec[1];
    
      // (D) den Vektor freigeben:
      vec.clear();  // "A" und "C" werden hier freigegeben
    
      // (E) das zweite Element "B" existiert noch: 
      anElement->Use();
      printf("fertig.\n");
    
      // (F) anElement verlässt den Gültigkeitsbereich und CSample("C") wird freigegeben
    }
    

    _________________________________________________________________________________

    shared_ptr<T> - Die wichtigsten Eigenschaften

    Es gibt unzählige Smart Pointer-Implementationen***. Einige*** wichtige Eigenschaften machen boost::shared_ptr empfehlenswert:

    • shared_ptr<T> kann für einen unvollständigen Typ T verwendet werden:
      class X; // forward declaration
      shared_ptr<X> pX; // "X *"
      Zum Dereferenzieren und Freigeben von pX muss X aber vollständig bekannt sein.
    • shared_ptr<T> kann man auf einen beliebigen Typen T anwenden:
      Es gibt praktisch keine Anforderungen an den Typen T (T muss z.B. nicht von einer vorgegebenen Basisklasse abgeleitet sein)
    • shared_ptr<T> erlaubt einen Custom Deleter
      Die automatische Freigabe des referenzierten Objekts erfolgt normalerweise mit delete. Man kann aber auch einen anderen Funktor~@Mr.B: das sind z.B. Klassen, die sich mittels Konstruktor und operator () wie Funktionen aufrufen lassen~ angeben***. Dadurch*** lassen sich beliebige Ressourcen in einem shared_ptr verwalten. Wichtig: der Deleter ist kein Template-Argument (was einer "Template-Infektion" vorbeugt).~??? Laut boost.org ist der Typ des Deleters durchaus ein Template-Argument, falls das gemeint war. Außerdem: was soll "Template-Infektion" bedeuten?~
    • Implizite Konvertierung:
      Wenn U * implizit in T * umgewandelt werden kann (z.B., weil T eine Basisklasse von U ist), kann ein shared_ptr<U> auch implizit in einen shared_ptr<T> umgewandelt werden.
    • shared_ptr ist threadsicher
      (Das ist zwar eher eine Entwurfsentscheidung als ein uneingeschränkter Vorteil, für Multithread-Programme aber unumgänglich und mit nur geringen Performance-Einbußen erkauft)
    • portabel, peer-reviewed, das Übliche.

    _________________________________________________________________________________

    Was kann schief gehen?

    Zwar sind Smart Pointer um einiges robuster als gewöhnliche Zeiger, aber wie immer gibt es einige Dinge, die man unbedingt wissen muss:

    Regel 1: Zuweisen und Halten - Ein mit new erzeugtes Objekt sollte sofort an einen Smart Pointer zugewiesen und ausschließlich über Smart Pointer referenziert werden. Die Smart Pointer sind nun Eigentümer des Objekts. Dadurch vermeidet man versehentliches vorzeitiges Löschen eines Objekts, das noch von anderen Zeigern referenziert wird.

    Regel 2: Ein _ptr<T> ist kein T * - genauer gesagt: es gibt keine implizite Konvertierung zwischen einem T * und einem shared_ptr<T>. Dadurch wird eine versehentliche Verletzung von Regel (1) ausgeschlossen. "Gefährliche" Operationen müssen daher explizit gemacht werden:

    • Ein Smart Pointer wird normalerweise mit ..._ptr<T> myPtr(new T) erzeugt.
    • Ein T * kann nicht an einen Smart Pointer zugewiesen werden.
    • Nicht einmal ptr=NULL ist erlaubt - stattdessen verwendet man ptr.reset().
    • Der normale Zeiger ist über ptr.get() verfügbar. Natürlich darf dieser Zeiger nur verwendet werden, solange der entsprechende Smart Pointer existiert, und auch nicht B. manuell freigegeben werden.**Satzkonstruktion** get() ist aber notwendig, wenn ein _ptr<T> das Objekt z.B. an eine Funktion übergeben soll, die einen T * erwartet.
    • An eine Funktion, die einen _ptr<T> erwartet, kann ein T * nicht implizit übergeben werden. Stattdessen erstellt man einen lokalen Smart Pointer - damit gibt man den "Besitz" explizit ab. (Achtung! Regel 3!)
    • Es gibt keine allgemein gültige[kor] Methode, den Smart Pointer, der einen bestimmten normalen Zeiger enthält, aufzufinden, jedoch stellen die [kor]Boost: Smart Pointer Programming Techniques Lösungen für häufige Situationen dar.

    Regel 3: keine temporären shared_ptr - immer schön eine Variable vergeben. (Notwendig für korrekte Exception-Behandlung, Details findet man unter boost: shared_ptr best practices)

    Regel 4: Keine zirkulären Referenzen - mehr dazu sofort.
    Generell sind Boost Smart Pointer auf Sicherheit getrimmt - potenziell gefährliche Operationen müssen explizit geschrieben werden. Dazu gleich mehr.
    _________________________________________________________________________________

    Zyklische Referenzen

    Referenzzählung erlaubt bequemes und effektives automatisches Ressourcenmanagement, hat aber eine Schwachstelle: Objekte, die sich (direkt oder indirekt) gegenseitig referenzieren. Ein einfaches Beispiel:

    struct CDad;
    struct CChild;
    
    typedef boost::shared_ptr<CDad>   CDadPtr;
    typedef boost::shared_ptr<CChild> CChildPtr;
    
    struct CDad   : public CSample { CChildPtr myBoy;  };
    struct CChild : public CSample { CDadPtr myDad;    };
    
    CDadPtr   parent(new CDadPtr); 
    CChildPtr child(new CChildPtr);
    
    // absichtlich eine zirkuläre Referenz erstellen:
    parent->myBoy = child; 
    child->myDad = dad;
    
    // einen Zeiger zurücksetzen...
    child.reset();
    

    Sowohl die CChild-Instanz als auch parent referenzieren die CDad-Instanz, welche wiederum die CChild-instanz referenziert:

    [*** Bild 4 ***]

    Wenn man jetzt dad.reset() aufruft, verlieren die beiden Objekte jeglichen Kontakt zur Außenwelt, halten sich aber gegenseitig am Leben - ein klassisches Leck! Im schlimmsten Fall werden dadurch kritische Ressourcen nicht mehr freigegeben.

    Diese Problem ist nicht (bzw. nur mit inakzeptablen Restriktionen) innerhalb der shared_ptr-Implementation lösbar - man muss den Kreis erkennen und selbst auflösen. Dazu gibt es verschiedene Wege:

    • Bevor man die letzte Referenz freigibt, wird der Kreis manuell aufgelöst (z.B. dad.ReleaseChild(); dad.reset(); ).
    • Wenn Dad eine längere Lebensdauer als Child hat, kann CChild einen gewöhnlichen CDad * verwenden
    • man verwendet boost::weak_ptr für CChild.myDad

    Die Lösungen (1) und (2) können nicht immer eingesetzt werden, funktionieren aber mit allen Smart Pointer - Bibliotheken. (3) ist eigentlich eine Verallgemeinerung von (2), die aber ohne eine Anforderung an die Lebensdauer auskommt.
    _________________________________________________________________________________

    Zyklische Referenzen mit weak_ptr aufbrechen

    Man unterscheidet starke und schwache Referenzen: Eine starke Referenz verhindert das Löschen eines Objekts, eine schwache Referenz tut dies nicht.
    In diesem Sinne ist boost::shared_ptr<T> eine starke und T * eine schwache Referenz. Mit T * kann man aber nicht prüfen, ob das Objekt noch existiert (daher die Lebensdauer-Anforderung in Lösung (2) oben).

    boost::weak_ptr<T> ist ein Smart Pointer, der eine schwache Referenz implementiert. Bei Bedarf kann man sich von einer solchen eine starke Referenz geben lassen. Falls das Objekt nicht mehr existiert, ist diese 0. Die starke Referenz darf natürlich nur solange wie nötig behalten werden.
    Obiges Beispiel mit eine weak_ptr:

    struct CBetterChild : public CSample
    {
      weak_ptr<CDad> myDad;
    
      void BringBeer()
      {
        shared_ptr<CDad> strongDad = myDad.lock(); // eine starke Referenz beantragen
        if (strongDad)                      // ist das Objekt noch existent?
          strongDad->SetBeer();
        // strongDad wird freigegeben, sobald sein Gültigkeitsbereich verlassen wird; die schwache Referenz wird behalten.
      }
    };
    

    Komplexe Objektstrukturen erfordern immer noch eine sorgfältige Analyse, an welchen Stellen zirkuläre Referenzen auftreten und welche davon durch einen weak_ptr aufgebrochen werden können (im Beispiel kann man z.B. nicht den Zeiger auf CChild durch einen weak_ptr ersetzen!)
    Hat man aber einmal eine ordentliche Struktur aufgebaut, muss man sich um die Freigabe keinen Kopf mehr machen - und gerade bei komplexen Strukturen ist das sehr angenehm.

    _________________________________________________________________________________

    intrusive_ptr<t> - Das Leichtgewicht

    shared_ptr bietet eine Menge mehr als einen "normalen" Zeiger. Das hat natürlich einen kleine Preis: Ein shared_ptr ist größer als ein normaler Zeiger, und für jedes referenzierte Objekt gibt es ein separates "Tracking"-Objekt mit dem Referenzzähler und dem Custom Deleter. In den meisten Fällen kann man dies vernachlässigen, jedoch nicht immer.

    boost::intrusive_ptr bietet eine interessante Alternative: Den "leichtmöglichsten" referenzgezählten Zeiger - allerdings muss man sich um das Zählen selbst kümmern. Das ist gar nicht so schlimm - will man seine eigenen Klassen Smart Pointer-kompatibel machen, packt man den Referenzzähler gleich mit rein. Dafür bekommt man weniger Allokationen und einen fixen Smart Pointer.

    Um einen Typ intrusive_ptr<T> zu verwenden, muss man zwei Funktionen definieren: intrusive_ptr_add_ref und intrusive_ptr_release. Das folgende Beispiel zeigt, wie:

    [kor]#include "boost/intrusive_ptr.hpp"[/kor]
    
    class CRefCounted
    {
      private:
        long    references;
        friend void intrusive_ptr_add_ref(T * p);
        friend void intrusive_ptr_release(T * p);
    
      public:
        CRefCount() : references(0) {}   // references mit 0 initialisieren
    };
    
    // die zwei Funktionsüberladungen müssen bei den meisten Compilern im boost-Namespace stehen:
    namespace boost  
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        // Referenzzähler für das Objekt *p inkrementieren
        ++(p->references);
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       // Referenzzähler dekrementieren und, wenn der Zähler 0 erreicht hat, Objekt löschen
       if (--(p->references) == 0)
         delete p;
      } 
    } // namespace boost
    

    Das ist die einfachste (und nicht threadsichere!) Implementation. Eine generische Implementation findet man nach kurzem Stöbern in der [kor]Boost Mailing List[kor].

    Tipp:
    Um das Beispiel unter Windows/VC threadsicher zu bekommen, verwendet man [kor]Folgendes[/kor]:

    extern "C" _InterlockedIncrement(LPLONG lpAddend);
    extern "C" _InterlockedDecrement(LPLONG lpAddend);
    #pragma intrinsic(_InterlockedIncrement, _InterlockedDecrement)  // werden inline expandiert!
    
    namespace boost
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        _InterlockedIncrement(&(p->references));
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       if (_InterlockedDecrement(&(p->references)) == 0)
         delete p;
      } 
    }
    

    _________________________________________________________________________________

    scoped_array und shared_array

    Die beiden sollen nicht unerwähnt bleiben - sie sind einem scoped_ptr bzw. shared_ptr sehr ähnlich, Syntax und Verhalten ähnelt aber einem mit new[] allozierten Zeiger (z.B. operator[], delete[] als Standard-Deleter). Hinweis: Keiner von beiden merkt sich die Länge; sie sind also als "Array-Ersatz" nur bedingt geeignet.

    _________________________________________________________________________________

    Boost installieren

    Die aktuellen Boost-Bibliotheken können von boost.org heruntergeladen werden. Für die Verwendung der Smart Pointer-Bibliothek ist keine Übersetzung notwendig.
    Das Boost-Wurzelverzeichnis enthält Release Notes und eine Menge Verzeichnisse für Dokumentation, Build Tools usw. Die eigentlichen Quellen befinden sich in dem Unterverzeichnis boost\.

    Ich füge das boost-Wurzelverzeichnis zu den Standard-Include-Pfaden hinzu.
    in VC6: Extras/Einstellungen, "Verzeichnisse" - Reiter, "Verzeichnisse für... Include-Dateien",
    in VC7: Extras/Einstellungen, Projekte / VC++ - Verzeichnisse, "Verzeichnisse für... Include-Dateien",

    Die #includes sehen dann so aus:

    #include <boost/smart_ptr.hpp>
    

    _________________________________________________________________________________

    Was ist mit std::auto_ptr?

    std::auto_ptr ist der einzige Smart Pointer im aktuellen Standard. Leider - denn auto_ptr erfüllt zwar seine Zweck, ist aber unnötig komplex und wird schnell falsch eingesetzt.

    auto_ptr in zwei Sätzen: Die letzte auto_ptr-Instanz, der der Zeiger zugewiesen wurde, gibt das referenzierte Objekt frei. Das ist aber nicht notwendigerweise die letzte vorhandene auto_ptr-Instanz.

    In den beiden häufigsten Anwendungsfällen kann und sollte man auf andere Smart Pointer ausweichen

    1) lokale Objekte / PIMPL ==> scoped_ptr

    class CBar;
    class CFoo
    {
       auto_ptr<CBar>   m_pBar;  // besser: scoped_ptr
       // ...
    }
    

    scoped_ptr verbietet die bei auto_ptr mögliche Zuweisung (die einen u.U. gefährlichen, weil versteckten Transfer of Ownership bedeutet)

    2) Rückgabewert aus Factory ==> shared_ptr

    class CBar;
    auto_ptr<CBar> MakeBar() { ... }  // besser: shared_ptr
    

    shared_ptr erlaubt die hier mögliche Zuweisung - und ist auch bei Mehrfachzuweisungen sicher. Das folgende Beispiel soll das illustrieren:

    auto_ptr<CFont> GetFont(EMyFavoriteFonts emff);
    ...
    auto_ptr<CFont> font = GetFont(emffCoolShadowedFont);
    widget1->font = font;  // ??
    widget2->font = font;  // ??
    

    Wer gibt das CFont-Objekt wieder frei? widget2 - selbst wenn widget1 noch existiert. Hier ist ein Referenzzähler sinnvoll - also verwendet man besser shared_ptr oder intrusive_ptr.

    _________________________________________________________________________________

    Links (alle in Englisch)

    Boost Smart Pointer Bibliothek
    Smart Pointer - Tipps & Tricks
    Herb Sutter über Smart Pointers
    Originaler Artikel
    Kreativer Missbrauch von shared_ptr<T>

    _________________________________________________________________________________

    Sept 05, 2004: Erste Version
    Aug. 2005: Deutsche Übersetzung



  • Ich bitte die letzte Anmerkund (Satzkonstruktion) noch zu beachten oder vielmehr zu berichtigen!

    Mr. B



  • @peterchen
    Hier nochmal zur Erinnerung:

    Der normale Zeiger ist über ptr.get() verfügbar. Natürlich darf dieser Zeiger nur verwendet werden, solange der entsprechende Smart Pointer existiert, und auch nicht B. manuell freigegeben werden.

    was soll das B. heißen? 😕



  • @audacia: danke - ganz besonders für die neue Rechtschreibung 🙂

    Eine Frage: du hast mehrfach Bindestriche korrigiert - einmal Leerzeichen drumrum hinzugefügt, ein anderes mal weggenommen. Hat das irgendewin System? Ich versuch's nur zu verstehen.

    @predator:
    das "manuell" ist ein dämliches und nutzloses Füllwort.

    Was ich sagen will:

    boost::shared_ptr<int> pi = new int(23);
    delete pi.get();   // darfs du nicht!
    

    Sollte ich das noch ausführen?

    (Insgesamt find' ich den Text stellenweise recht holperig - aber ich glaub' jetzt nochmal alles zu überarbeiten wäre dämlich)



  • peterchen schrieb:

    @audacia: danke - ganz besonders für die neue Rechtschreibung 🙂

    Eine Frage: du hast mehrfach Bindestriche korrigiert - einmal Leerzeichen drumrum hinzugefügt, ein anderes mal weggenommen. Hat das irgendewin System? Ich versuch's nur zu verstehen.

    Eigentlich habe ich solche Leerzeichen nur weggenommen, und zwar dann, wenn sie zwei Wörter verbanden (z.B. OpenSource-Bibliothekssammlung) und nicht zwei Sätze, da die Wörter IMHO enger zusammengehören als Sätze. Allerdings habe ich gerade noch ein paar Stellen gefunden, wo ich ebendieses vergaß; das wird gleich korrigiert.
    Fändest du die Bindestriche in der ursprünglichen Form korrekter?



  • Inhalt

    Smart Pointer können komplexe Anwendungen in C++ wesentlich vereinfachen. Sie bieten hauptsächlich eine automatische Speicherverwaltung ähnlich derer in restriktiveren Sprachen (z.B. VB, C#), sind aber auch in anderen Situationen hilfreich.

    _________________________________________________________________________________

    Was sind Smart Pointer?

    Ein Smart Pointer verhält sich wie ein Zeiger, gibt das referenzierte Objekt aber automatisch frei, wenn es nicht mehr benötigt wird.

    "Nicht mehr benötigt" lässt sich in C++ schwer definieren. Daher verwendet man verschiedene Smart Pointer-Implementationen, die auf die häufigsten Szenarien angepasst sind. Neben dem automatischen Freigeben sind natürlich noch andere Anwendungen möglich.

    Smart Pointer-Implementationen findet man in vielen Bibliotheken - jede mit ihren eigenen Vorzügen und Problemen. Dieser Artikel bezieht sich auf die Smart Pointer der Boost-Bibliotheken, einer hochwertigen OpenSource-Bibliothekssammlung, von denen einige für die Einbindung in den nächsten C++-Standard vorgesehen sind.

    Boost bietet die folgenden Implementationen:

    • shared_ptr<T>
      Zeiger auf T. Ein Referenzzähler bestimmt, wann das Objekt gelöscht werden muss
      shared_ptr ist der flexibelste Smart Pointer aus boost.
    • scoped_ptr<T>
      Zeiger auf T, das referenzierte Objekt wird gelöscht wenn der scoped_ptr den Gültigkeitsbereich verlässt.
      Gleiche Performance wie T *. Keine Zuweisung an andere scoped_ptr-Instanz möglich.
    • intrusive_ptr<T>
      Ein weiterer referenzgezählter Zeiger. Bessere Performance als shared_ptr, aber die Referenzzählung muss separat implementiert sein. Damit lassen sich aber auch "fremde" Referenzzähler (z.B. COM) unterbringen.
    • weak_ptr<T>
      Ein schwacher Zeiger, zusammen mit shared_ptr zum Aufbrechen zirkulärer Referenzen
    • shared_array<T>
      wie shared_ptr, aber Zugriff ist wie auf Array von T
    • scoped_array<T>
      wie scoped_ptr, aber Zugriff ist wie auf Array von T

    _________________________________________________________________________________

    Ganz einfach: boost::scoped_ptr<T>

    scoped_ptr ist der einfachste Boost Smart Pointer. Er garantiert automatisches Löschen, wenn der Zeiger den Gültigkeitsbereich verlä***ss***t.

    ~Hinweis zum Beispielcode:
    Die Beispiele verwenden eine Hilfsklasse CSample, die für Konstruktion, Zuweisung usw. Meldungen ausgibt. Trotzdem ist es sicherlich interessant, die Beispiele unter dem Debugger nachzuvollziehen. Die Quellen enthalten die notwendigen [kor]Boost[/kor]-Header (bitte "Boost installieren" weiter unten beachten!)~

    Beispiel 1 - mit normalen Zeigern

    void Sample1_Plain()
    {
      CSample * pSample(new CSample);
    
      if (!pSample->Query() ) // irgendeine Funktion...
      {
        delete pSample;
        return;
      }
    
      pSample->Use();
      delete pSample;
    }
    

    Beispiel 2 - mit scoped_ptr<T>

    #include "boost/smart_ptr.h"
    
    void Sample1_ScopedPtr()
    {
      boost::scoped_ptr<CSample> samplePtr(new CSample);
    
      if (!samplePtr->Query() ) // irgendeine Funktion...
        return;    
    
      samplePtr->Use();
    }
    

    Einen normalen Zeiger muss man an allen Stellen freigeben, an denen man die Funktion verlässt. Das ist leicht zu übersehen, wenn man Exceptions verwendet. Verwendet man wie im zweiten Beispiel einen scoped_ptr, wird er automatisch freigegeben - auch wenn CSample::Query eine Exception wirft!

    Der Vorteil ist nicht zu übersehen: In einer komplexeren Funktion wird ein delete schnell vergessen - besonders, wenn man ein bisschen aufräumt.

    scoped_ptr eignet sich für: Automatische Freigabe von lokalen Objekten und Klassendaten, verzögerte Initialisierunng, Implementierung von PIMPL und RAII
    was geht nicht: Als Element in STL-Container, mehrere Zeiger auf das gleiche Objekt, andere Allokatoren als new/delete
    Performance: Praktisch identisch mit normalem Zeiger

    ~Tipp: Wer PIMPL (pointer to implementation, auch handle/body) und RAII (Resource Acquisition Is Initialization) nicht kennt, sollte sich ganz fix ein gutes C++-Buch [kor]zur Hand nehmen[/kor] und diese wichtigen Konzepte nachholen. Smart Pointer sind nur eine (bequeme) Möglichkeit, diese zu implementieren.~

    scoped_ptr verbietet direkte implizite Zuweisungen:

    scoped_ptr<CSample> ptrA(new CSample);
    scoped_ptr<CSample> ptrB = ptrA; // Compile-Fehler!  
    ptrA = new CSample;              // Compile-Fehler!
    

    Das vermeidet die üblichen Fehler im Umgang mit Smart Pointern, die meistens am Übergang zwischen Smart Pointer und normalem Zeiger auftreten. Explizit darf man natürlich alles (und ist selbst schuld, wenn es schief geht):

    T* scoped_ptr<T>::get()      // gibt den enthaltenen Zeiger zurück
    scoped_ptr<T>::reset(T *)    // ersetzt den enthaltenen Zeiger mit einer neuen Instanz. 
                                 // die vorige Instanz wird dabei freigegeben
    

    _________________________________________________________________________________

    Referenzzähler und shared_ptr<T>

    Wenn man mehrere Smart Pointer auf das gleiche Objekt zulassen möchte, benötigt man ein anderes Kriterium für "nicht mehr benötigt".
    Referenzgezählte Zeiger überwachen, wie viele Zeiger auf ein Objekt verweisen. Dazu wird jedem Objekt ein Zähler zugeordnet. Dieser wird bei einer Zeigerzuweisung erhöht, und im Zeigerdestruktor runtergezählt. Wenn der Zähler 0 erreicht, wird das Objekt gelöscht.

    Boost bietet shared_ptr als referenzgezählten Zeiger und "kleinen Alleskönner". Zuerst ein kleines Beispiel:

    void Sample2_Shared()
    {
      // (A) Erzeugen einer [kor]CSample-Instanz[/kor] mit einer Referenz
      boost::shared_ptr<CSample> p1(new CSample); 
      printf("The Sample now has %i references\n", p1.use_count()); // use_count() == 1
    
      // (B) An zweiten Zeiger zuweisen:
      boost::shared_ptr<CSample> p2 = mySample; 
      printf("The Sample now has %i references\n", p2.use_count()); // use_count() == 2
    
      // (C) den ersten Zeiger auf NULL setzen
      p1.reset(); 
      printf("The Sample now has %i references\n", p2.use_count());  // use_count() == 1
    
      // Das bei (A) allozierte Objekt wird freigegeben, wenn p2 den Gültigkeitsbereich verlässt
    }
    

    Zeile (A) erzeugt eine CSample-Instanz auf dem Heap und speichert einen Zeiger darauf in dem shared_ptr mySample. Das sieht dann so aus:

    [*** Bild 1 ***]

    In (B) wird ein zweiter Zeiger auf das Objekt angelegt:

    [*** Bild 2 ***]

    mySample wird mittels reset() auf NULL gesetzt (reset() ist identisch p=NULL für einen normalen Zeiger). CSample wird noch von p2 referenziert:

    [*** Bild 3 ***]

    Erst wenn die letzte Referenz auf CSample verschwindet, wird CSample gelöscht:

    [*** Bild 3 ***]

    Häufige Anwendungsfälle sind:

    • p1 und p2 in obigem Beispiel haben voneinander unabhängige Lebensdauer
    • Container von Zeigern (z.B. ein std::vector< boost::shared_ptr<MyUncopyableClass> >)
    • PIMPL + RAII für von mehreren unabhängigen Clients genutzten Ressourcen / Objekten

    _________________________________________________________________________________

    Beispiel für shared_ptr<T>: shared_ptr im STL-Container

    Viele Container-Klassen (u.a. STl-Container) erfordern Kopieroperationen, z.B. wenn ein existierendes Element in eine Liste oder einen Vektor eingefügt werden soll. Das ist für komplexe Klassen ungünstig und für manche Klassen sogar unmöglich. In diesem Fall weicht man üblicherweise auf einen Container von Zeigern aus:

    std::vector<CMyLargeClass *> vec;
    vec.push_back( new CMyLargeClass("dicke fette Zeichenkette") );
    

    Damit ist man aber als "Nutzer" wieder verantwortlich für die Speicherverwaltung. Mit einem shared_ptr wird es wieder einfacher:

    typedef boost::shared_ptr<CMyLargeClass>  CMyLargeClassPtr;
    std::vector<CMyLargeClassPtr> vec;
    vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
    

    Sehr ähnlich, aber nun werden die Container-Elemente wieder automatisch mit dem Container freigegeben - es sei denn, es gibt noch einen anderen shared_ptr auf ein Element, wie im Folgenden demonstriert:

    void Sample3_Container()
    {
      typedef boost::shared_ptr<CSample> CSamplePtr;
    
      // (A) Vektor mit CSample-Zeigern:
      std::vector<CSamplePtr> vec;
    
      // (B) drei Elemente hinzufügen
      vec.push_back(CSamplePtr(new CSample("A"));
      vec.push_back(CSamplePtr(new CSample("B"));
      vec.push_back(CSamplePtr(new CSample("C"));
    
      // (C) [kor]Einen Zeiger auf dem zweiten[/kor] Element "behalten": 
      CSamplePtr anElement = vec[1];
    
      // (D) den Vektor freigeben:
      vec.clear();  // "A" und "C" werden hier freigegeben
    
      // (E) das zweite Element "B" existiert noch: 
      anElement->Use();
      printf("fertig.\n");
    
      // (F) anElement verlässt den Gültigkeitsbereich und CSample("C") wird freigegeben
    }
    

    _________________________________________________________________________________

    shared_ptr<T> - Die wichtigsten Eigenschaften

    Es gibt unzählige Smart Pointer-Implementationen***. Einige*** wichtige Eigenschaften machen boost::shared_ptr empfehlenswert:

    • shared_ptr<T> kann für einen unvollständigen Typ T verwendet werden:
      class X; // forward declaration
      shared_ptr<X> pX; // "X *"
      Zum Dereferenzieren und Freigeben von pX muss X aber vollständig bekannt sein.
    • shared_ptr<T> kann man auf einen beliebigen Typen T anwenden:
      Es gibt praktisch keine Anforderungen an den Typen T (T muss z.B. nicht von einer vorgegebenen Basisklasse abgeleitet sein)
    • shared_ptr<T> erlaubt einen Custom Deleter
      Die automatische Freigabe des referenzierten Objekts erfolgt normalerweise mit delete. Man kann aber auch einen anderen Funktor~@Mr.B: das sind z.B. Klassen, die sich mittels Konstruktor und operator () wie Funktionen aufrufen lassen~ angeben***. Dadurch*** lassen sich beliebige Ressourcen in einem shared_ptr verwalten. Wichtig: der Deleter ist kein Template-Argument (was einer "Template-Infektion" vorbeugt).~??? Laut boost.org ist der Typ des Deleters durchaus ein Template-Argument, falls das gemeint war. Außerdem: was soll "Template-Infektion" bedeuten?~
    • Implizite Konvertierung:
      Wenn U * implizit in T * umgewandelt werden kann (z.B., weil T eine Basisklasse von U ist), kann ein shared_ptr<U> auch implizit in einen shared_ptr<T> umgewandelt werden.
    • shared_ptr ist threadsicher
      (Das ist zwar eher eine Entwurfsentscheidung als ein uneingeschränkter Vorteil, für Multithread-Programme aber unumgänglich und mit nur geringen Performance-Einbußen erkauft)
    • portabel, peer-reviewed, das Übliche.

    _________________________________________________________________________________

    Was kann schief gehen?

    Zwar sind Smart Pointer um einiges robuster als gewöhnliche Zeiger, aber wie immer gibt es einige Dinge, die man unbedingt wissen muss:

    Regel 1: Zuweisen und Halten - Ein mit new erzeugtes Objekt sollte sofort an einen Smart Pointer zugewiesen und ausschließlich über Smart Pointer referenziert werden. Die Smart Pointer sind nun Eigentümer des Objekts. Dadurch vermeidet man versehentliches vorzeitiges Löschen eines Objekts, das noch von anderen Zeigern referenziert wird.

    Regel 2: Ein _ptr<T> ist kein T * - genauer gesagt: es gibt keine implizite Konvertierung zwischen einem T * und einem shared_ptr<T>. Dadurch wird eine versehentliche Verletzung von Regel (1) ausgeschlossen. "Gefährliche" Operationen müssen daher explizit gemacht werden:

    • Ein Smart Pointer wird normalerweise mit ..._ptr<T> myPtr(new T) erzeugt.
    • Ein T * kann nicht an einen Smart Pointer zugewiesen werden.
    • Nicht einmal ptr=NULL ist erlaubt - stattdessen verwendet man ptr.reset().
    • Der normale Zeiger ist über ptr.get() verfügbar. Natürlich darf dieser Zeiger nur verwendet werden, solange der entsprechende Smart Pointer existiert, und auch nicht B. manuell freigegeben werden.**Satzkonstruktion** get() ist aber notwendig, wenn ein _ptr<T> das Objekt z.B. an eine Funktion übergeben soll, die einen T * erwartet.
    • An eine Funktion, die einen _ptr<T> erwartet, kann ein T * nicht implizit übergeben werden. Stattdessen erstellt man einen lokalen Smart Pointer - damit gibt man den "Besitz" explizit ab. (Achtung! Regel 3!)
    • Es gibt keine allgemein gültige[kor] Methode, den Smart Pointer, der einen bestimmten normalen Zeiger enthält, aufzufinden, jedoch stellen die [kor]Boost: Smart Pointer Programming Techniques Lösungen für häufige Situationen dar.

    Regel 3: keine temporären shared_ptr - immer schön eine Variable vergeben. (Notwendig für korrekte Exception-Behandlung, Details findet man unter boost: shared_ptr best practices)

    Regel 4: Keine zirkulären Referenzen - mehr dazu sofort.
    Generell sind Boost Smart Pointer auf Sicherheit getrimmt - potenziell gefährliche Operationen müssen explizit geschrieben werden. Dazu gleich mehr.
    _________________________________________________________________________________

    Zyklische Referenzen

    Referenzzählung erlaubt bequemes und effektives automatisches Ressourcenmanagement, hat aber eine Schwachstelle: Objekte, die sich (direkt oder indirekt) gegenseitig referenzieren. Ein einfaches Beispiel:

    struct CDad;
    struct CChild;
    
    typedef boost::shared_ptr<CDad>   CDadPtr;
    typedef boost::shared_ptr<CChild> CChildPtr;
    
    struct CDad   : public CSample { CChildPtr myBoy;  };
    struct CChild : public CSample { CDadPtr myDad;    };
    
    CDadPtr   parent(new CDadPtr); 
    CChildPtr child(new CChildPtr);
    
    // absichtlich eine zirkuläre Referenz erstellen:
    parent->myBoy = child; 
    child->myDad = dad;
    
    // einen Zeiger zurücksetzen...
    child.reset();
    

    Sowohl die CChild-Instanz als auch parent referenzieren die CDad-Instanz, welche wiederum die CChild-instanz referenziert:

    [*** Bild 4 ***]

    Wenn man jetzt dad.reset() aufruft, verlieren die beiden Objekte jeglichen Kontakt zur Außenwelt, halten sich aber gegenseitig am Leben - ein klassisches Leck! Im schlimmsten Fall werden dadurch kritische Ressourcen nicht mehr freigegeben.

    Diese Problem ist nicht (bzw. nur mit inakzeptablen Restriktionen) innerhalb der shared_ptr-Implementation lösbar - man muss den Kreis erkennen und selbst auflösen. Dazu gibt es verschiedene Wege:

    • Bevor man die letzte Referenz freigibt, wird der Kreis manuell aufgelöst (z.B. dad.ReleaseChild(); dad.reset(); ).
    • Wenn Dad eine längere Lebensdauer als Child hat, kann CChild einen gewöhnlichen CDad * verwenden
    • man verwendet boost::weak_ptr für CChild.myDad

    Die Lösungen (1) und (2) können nicht immer eingesetzt werden, funktionieren aber mit allen Smart Pointer-Bibliotheken. (3) ist eigentlich eine Verallgemeinerung von (2), die aber ohne eine Anforderung an die Lebensdauer auskommt.
    _________________________________________________________________________________

    Zyklische Referenzen mit weak_ptr aufbrechen

    Man unterscheidet starke und schwache Referenzen: Eine starke Referenz verhindert das Löschen eines Objekts, eine schwache Referenz tut dies nicht.
    In diesem Sinne ist boost::shared_ptr<T> eine starke und T * eine schwache Referenz. Mit T * kann man aber nicht prüfen, ob das Objekt noch existiert (daher die Lebensdauer-Anforderung in Lösung (2) oben).

    boost::weak_ptr<T> ist ein Smart Pointer, der eine schwache Referenz implementiert. Bei Bedarf kann man sich von einer solchen eine starke Referenz geben lassen. Falls das Objekt nicht mehr existiert, ist diese 0. Die starke Referenz darf natürlich nur solange wie nötig behalten werden.
    Obiges Beispiel mit eine weak_ptr:

    struct CBetterChild : public CSample
    {
      weak_ptr<CDad> myDad;
    
      void BringBeer()
      {
        shared_ptr<CDad> strongDad = myDad.lock(); // eine starke Referenz beantragen
        if (strongDad)                      // ist das Objekt noch existent?
          strongDad->SetBeer();
        // strongDad wird freigegeben, sobald sein Gültigkeitsbereich verlassen wird; die schwache Referenz wird behalten.
      }
    };
    

    Komplexe Objektstrukturen erfordern immer noch eine sorgfältige Analyse, an welchen Stellen zirkuläre Referenzen auftreten und welche davon durch einen weak_ptr aufgebrochen werden können (im Beispiel kann man z.B. nicht den Zeiger auf CChild durch einen weak_ptr ersetzen!)
    Hat man aber einmal eine ordentliche Struktur aufgebaut, muss man sich um die Freigabe keinen Kopf mehr machen - und gerade bei komplexen Strukturen ist das sehr angenehm.

    _________________________________________________________________________________

    intrusive_ptr<t> - Das Leichtgewicht

    shared_ptr bietet eine Menge mehr als einen "normalen" Zeiger. Das hat natürlich einen kleine Preis: Ein shared_ptr ist größer als ein normaler Zeiger, und für jedes referenzierte Objekt gibt es ein separates "Tracking"-Objekt mit dem Referenzzähler und dem Custom Deleter. In den meisten Fällen kann man dies vernachlässigen, jedoch nicht immer.

    boost::intrusive_ptr bietet eine interessante Alternative: Den "leichtmöglichsten" referenzgezählten Zeiger - allerdings muss man sich um das Zählen selbst kümmern. Das ist gar nicht so schlimm - will man seine eigenen Klassen Smart Pointer-kompatibel machen, packt man den Referenzzähler gleich mit rein. Dafür bekommt man weniger Allokationen und einen fixen Smart Pointer.

    Um einen Typ intrusive_ptr<T> zu verwenden, muss man zwei Funktionen definieren: intrusive_ptr_add_ref und intrusive_ptr_release. Das folgende Beispiel zeigt, wie:

    [kor]#include "boost/intrusive_ptr.hpp"[/kor]
    
    class CRefCounted
    {
      private:
        long    references;
        friend void intrusive_ptr_add_ref(T * p);
        friend void intrusive_ptr_release(T * p);
    
      public:
        CRefCount() : references(0) {}   // references mit 0 initialisieren
    };
    
    // die zwei Funktionsüberladungen müssen bei den meisten Compilern im boost-Namespace stehen:
    namespace boost  
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        // Referenzzähler für das Objekt *p inkrementieren
        ++(p->references);
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       // Referenzzähler dekrementieren und, wenn der Zähler 0 erreicht hat, Objekt löschen
       if (--(p->references) == 0)
         delete p;
      } 
    } // namespace boost
    

    Das ist die einfachste (und nicht threadsichere!) Implementation. Eine generische Implementation findet man nach kurzem Stöbern in der [kor]Boost Mailing List[kor].

    Tipp:
    Um das Beispiel unter Windows/VC threadsicher zu bekommen, verwendet man [kor]Folgendes[/kor]:

    extern "C" _InterlockedIncrement(LPLONG lpAddend);
    extern "C" _InterlockedDecrement(LPLONG lpAddend);
    #pragma intrinsic(_InterlockedIncrement, _InterlockedDecrement)  // werden inline expandiert!
    
    namespace boost
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        _InterlockedIncrement(&(p->references));
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       if (_InterlockedDecrement(&(p->references)) == 0)
         delete p;
      } 
    }
    

    _________________________________________________________________________________

    scoped_array und shared_array

    Die beiden sollen nicht unerwähnt bleiben - sie sind einem scoped_ptr bzw. shared_ptr sehr ähnlich, Syntax und Verhalten ähnelt aber einem mit new[] allozierten Zeiger (z.B. operator[], delete[] als Standard-Deleter). Hinweis: Keiner von beiden merkt sich die Länge; sie sind also als "Array-Ersatz" nur bedingt geeignet.

    _________________________________________________________________________________

    Boost installieren

    Die aktuellen Boost-Bibliotheken können von boost.org heruntergeladen werden. Für die Verwendung der Smart Pointer-Bibliothek ist keine Übersetzung notwendig.
    Das Boost-Wurzelverzeichnis enthält Release Notes und eine Menge Verzeichnisse für Dokumentation, Build Tools usw. Die eigentlichen Quellen befinden sich in dem Unterverzeichnis boost\.

    Ich füge das boost-Wurzelverzeichnis zu den Standard-Include-Pfaden hinzu.
    in VC6: Extras/Einstellungen, "Verzeichnisse"-Reiter, "Verzeichnisse für... Include-Dateien"
    in VC7: Extras/Einstellungen, Projekte/VC++-Verzeichnisse, "Verzeichnisse für... Include-Dateien"

    Die #includes sehen dann so aus:

    #include <boost/smart_ptr.hpp>
    

    _________________________________________________________________________________

    Was ist mit std::auto_ptr?

    std::auto_ptr ist der einzige Smart Pointer im aktuellen Standard. Leider - denn auto_ptr erfüllt zwar seine Zweck, ist aber unnötig komplex und wird schnell falsch eingesetzt.

    auto_ptr in zwei Sätzen: Die letzte auto_ptr-Instanz, der der Zeiger zugewiesen wurde, gibt das referenzierte Objekt frei. Das ist aber nicht notwendigerweise die letzte vorhandene auto_ptr-Instanz.

    In den beiden häufigsten Anwendungsfällen kann und sollte man auf andere Smart Pointer ausweichen:

    1) lokale Objekte / PIMPL ==> scoped_ptr

    class CBar;
    class CFoo
    {
       auto_ptr<CBar>   m_pBar;  // besser: scoped_ptr
       // ...
    }
    

    scoped_ptr verbietet die bei auto_ptr mögliche Zuweisung (die einen u.U. gefährlichen, weil versteckten Transfer of Ownership bedeutet)

    2) Rückgabewert aus Factory ==> shared_ptr

    class CBar;
    auto_ptr<CBar> MakeBar() { ... }  // besser: shared_ptr
    

    shared_ptr erlaubt die hier mögliche Zuweisung - und ist auch bei Mehrfachzuweisungen sicher. Das folgende Beispiel soll das illustrieren:

    auto_ptr<CFont> GetFont(EMyFavoriteFonts emff);
    ...
    auto_ptr<CFont> font = GetFont(emffCoolShadowedFont);
    widget1->font = font;  // ??
    widget2->font = font;  // ??
    

    Wer gibt das CFont-Objekt wieder frei? widget2 - selbst wenn widget1 noch existiert. Hier ist ein Referenzzähler sinnvoll - also verwendet man besser shared_ptr oder intrusive_ptr.

    _________________________________________________________________________________

    Links (alle in Englisch)

    Boost Smart Pointer Bibliothek
    Smart Pointer - Tipps & Tricks
    Herb Sutter über Smart Pointers
    Originaler Artikel
    Kreativer Missbrauch von shared_ptr<T>

    _________________________________________________________________________________

    Sept 05, 2004: Erste Version
    Aug. 2005: Deutsche Übersetzung



  • @audacia:

    Zu deiner Anmerkung

    Der Typ des Deleters ist zwar ein template-Parameter des entsprechenden Konstruktors, aber kein Template-Parameter der shared_ptr - Klasse.

    d.h. ich einer Funktion

    void foo(shared_ptr<int> p)
    

    shared_ptr - Instanzen mit beliebigen Deletern als Argumente mitgeben.

    Wäre der Delter ein Template-parameter von shared_ptr, müßte auch foo ein Template sein:

    template <typename D> void foo(shared_ptr<int, D> p)
    

    was nicht immer praktikabel ist.

    Sollte ich den Zweig ganz raus lassen? Für mich ist Letzteres ein ganz großer Pluspunkt, aber das ist vielleicht nicht so wichtig.

    Wenn ich das jetzt update - soll ich dann alle <kor>-</kor>'s rausnehmen?



  • @peterchen: Danke für die Erklärung.

    peterchen schrieb:

    Sollte ich den Zweig ganz raus lassen? Für mich ist Letzteres ein ganz großer Pluspunkt, aber das ist vielleicht nicht so wichtig.

    Ich würde es drinlassen, aber vielleicht etwas konkretisieren.



  • Inhalt

    Smart Pointer können komplexe Anwendungen in C++ wesentlich vereinfachen. Sie bieten eine automatische Speicherverwaltung ähnlich derer in restriktiveren Sprachen (z.B. VB, C#), sind aber auch in anderen Situationen hilfreich.

    _________________________________________________________________________________

    Was sind Smart Pointer?

    Ein Smart Pointer verhält sich wie ein Zeiger, gibt das referenzierte Objekt aber automatisch frei, wenn es nicht mehr benötigt wird.

    "Nicht mehr benötigt" lässt sich in C++ schwer definieren. Daher gibt es verschiedene Smart Pointer-Implementationen, die auf die häufigsten Szenarien angepasst sind. Neben dem automatischen Freigeben sind natürlich noch andere Anwendungen möglich.

    Smart Pointer-Implementationen findet man in vielen Bibliotheken - jede mit ihren eigenen Vorzügen und Problemen. Dieser Artikel verwendet die Smart Pointer der Boost-Bibliotheken, einer hochwertigen OpenSource-Bibliothekssammlung, von denen einige für die Einbindung in den nächsten C++-Standard vorgesehen sind.

    Boost bietet die folgenden Implementationen:

    • shared_ptr<T>
      Zeiger auf T. Ein Referenzzähler bestimmt, wann das Objekt gelöscht werden muss shared_ptr ist der flexibelste Smart Pointer aus boost.
    • scoped_ptr<T>
      Zeiger auf T, das referenzierte Objekt wird gelöscht wenn der scoped_ptr den Gültigkeitsbereich verlässt. Gleiche Performance wie T *. Keine Zuweisung an andere scoped_ptr-Instanz möglich.
    • intrusive_ptr<T>
      Ein weiterer referenzgezählter Zeiger. Bessere Performance als shared_ptr, aber die Referenzzählung muss separat implementiert sein. Damit lassen sich "fremde" Referenzzähler (z.B. COM) unterbringen.
    • weak_ptr<T>
      Ein schwacher Zeiger, zusammen mit shared_ptr zum Aufbrechen zirkulärer Referenzen
    • shared_array<T>
      wie shared_ptr, aber Zugriff ist wie auf Array von T
    • scoped_array<T>
      wie scoped_ptr, aber Zugriff ist wie auf Array von T

    _________________________________________________________________________________

    Ganz einfach: boost::scoped_ptr<T>

    scoped_ptr ist der einfachste Boost Smart Pointer. Er garantiert automatisches Löschen, wenn der Zeiger den Gültigkeitsbereich verlässt.

    ~Hinweis zum Beispielcode:
    Die Beispiele verwenden eine Hilfsklasse CSample, die für Konstruktion, Zuweisung usw. Meldungen ausgibt. [kor]Sicherlich ist es trotzdem[/kor]interessant, die Beispiele unter dem Debugger nachzuvollziehen. Die Quellen enthalten die notwendigen Boost-Header (bitte "Boost installieren" weiter unten beachten!)~

    Beispiel 1 - mit normalen Zeigern

    void Sample1_Plain()
    {
      CSample * pSample(new CSample);
    
      if (!pSample->Query() ) // irgendeine Funktion...
      {
        delete pSample;
        return;
      }
    
      pSample->Use();
      delete pSample;
    }
    

    Beispiel 2 - mit scoped_ptr<T>

    #include "boost/smart_ptr.h"
    
    void Sample1_ScopedPtr()
    {
      boost::scoped_ptr<CSample> samplePtr(new CSample);
    
      if (!samplePtr->Query() ) // irgendeine Funktion...
        return;    
    
      samplePtr->Use();
    }
    

    Einen normalen Zeiger muss man an allen Stellen freigeben, an denen man die Funktion verlässt. Das ist leicht zu übersehen, wenn man Exceptions verwendet. Ein scoped_ptr wie im zweiten Beispiel wird automatisch freigegeben - auch wenn CSample::Query eine Exception wirft!

    Der Vorteil ist nicht zu übersehen: In einer komplexeren Funktion wird ein delete schnell vergessen - besonders, wenn man ein bisschen aufräumt.

    scoped_ptr eignet sich für: Automatische Freigabe von lokalen Objekten und Klassendaten, verzögerte Initialisierunng, Implementierung von PIMPL und RAII
    was geht nicht: Als Element in STL-Container, mehrere Zeiger auf das gleiche Objekt, andere Allokatoren als new/delete
    Performance: Praktisch identisch mit normalem Zeiger

    ~Tipp: Wer PIMPL (pointer to implementation, auch handle/body) und RAII (Resource Acquisition Is Initialization) nicht kennt, [kor]sollte ganz fix[/kor] ein gutes C++-Buch zur Hand nehmen und diese wichtigen Konzepte nachholen. Smart Pointer sind nur eine (bequeme) Möglichkeit, diese zu implementieren.~

    scoped_ptr verbietet direkte implizite Zuweisungen:

    scoped_ptr<CSample> ptrA(new CSample);
    scoped_ptr<CSample> ptrB = ptrA; // Compile-Fehler!  
    ptrA = new CSample;              // Compile-Fehler!
    

    Das vermeidet die üblichen Fehler im Umgang mit Smart Pointern, die meistens am Übergang zwischen Smart Pointer und normalem Zeiger auftreten. Explizit darf man natürlich alles (und ist selbst schuld, wenn es schief geht):

    T* scoped_ptr<T>::get()      // gibt den enthaltenen Zeiger zurück
    scoped_ptr<T>::reset(T *)    // ersetzt den enthaltenen Zeiger mit einer neuen Instanz. 
                                 // die vorige Instanz wird dabei freigegeben
    

    _________________________________________________________________________________

    Referenzzähler und shared_ptr<T>

    Wenn man mehrere Smart Pointer auf das gleiche Objekt zulassen möchte, benötigt man ein anderes Kriterium für "nicht mehr benötigt".
    Referenzgezählte Zeiger überwachen, wie viele Zeiger auf ein Objekt verweisen. Dazu wird jedem Objekt ein Zähler zugeordnet. Dieser wird bei einer Zeigerzuweisung erhöht, und im Zeigerdestruktor runtergezählt. Wenn der Zähler 0 erreicht, wird das Objekt gelöscht.

    Boost bietet shared_ptr als referenzgezählten Zeiger und "kleinen Alleskönner". Zuerst ein kleines Beispiel:

    void Sample2_Shared()
    {
      // (A) Erzeugen einer [kor]CSample-Instanz[/kor] mit einer Referenz
      boost::shared_ptr<CSample> p1(new CSample); 
      printf("The Sample now has %i references\n", p1.use_count()); // use_count() == 1
    
      // (B) An zweiten Zeiger zuweisen:
      boost::shared_ptr<CSample> p2 = mySample; 
      printf("The Sample now has %i references\n", p2.use_count()); // use_count() == 2
    
      // (C) den ersten Zeiger auf NULL setzen
      p1.reset(); 
      printf("The Sample now has %i references\n", p2.use_count());  // use_count() == 1
    
      // Das bei (A) allozierte Objekt wird freigegeben, wenn p2 den Gültigkeitsbereich verlässt
    }
    

    Zeile (A) erzeugt eine CSample-Instanz auf dem Heap und speichert einen Zeiger darauf in dem shared_ptr mySample. Das sieht dann so aus:

    In (B) wird ein zweiter Zeiger auf das Objekt angelegt:

    mySample wird mittels reset() auf NULL gesetzt (reset() ist identisch p=NULL für einen normalen Zeiger). CSample wird noch von p2 referenziert:

    Erst wenn die letzte Referenz auf CSample verschwindet, wird CSample gelöscht:

    Häufige Anwendungsfälle sind:

    • p1 und p2 in obigem Beispiel haben voneinander unabhängige Lebensdauer
    • Container von Zeigern (z.B. ein std::vector< boost::shared_ptr<MyUncopyableClass> >)
    • PIMPL + RAII für von mehreren unabhängigen Clients genutzten Ressourcen / Objekten

    _________________________________________________________________________________

    Beispiel für shared_ptr<T>: shared_ptr im STL-Container

    Viele Container-Klassen (u.a. STl-Container) erfordern Kopieroperationen, z.B. wenn ein existierendes Element in eine Liste oder einen Vektor eingefügt werden soll. Das ist für komplexe Klassen ungünstig und für manche Klassen sogar unmöglich. In diesem Fall weicht man üblicherweise auf einen Container von Zeigern aus:

    std::vector<CMyLargeClass *> vec;
    vec.push_back( new CMyLargeClass("dicke fette Zeichenkette") );
    

    Damit ist man aber als "Nutzer" wieder verantwortlich für die Speicherverwaltung. Mit einem shared_ptr wird es wieder einfacher:

    typedef boost::shared_ptr<CMyLargeClass>  CMyLargeClassPtr;
    std::vector<CMyLargeClassPtr> vec;
    vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
    

    Sehr ähnlich, aber nun werden die Container-Elemente wieder automatisch mit dem Container freigegeben - es sei denn, es gibt noch einen anderen shared_ptr auf ein Element, wie im Folgenden demonstriert:

    void Sample3_Container()
    {
      typedef boost::shared_ptr<CSample> CSamplePtr;
    
      // (A) Vektor mit CSample-Zeigern:
      std::vector<CSamplePtr> vec;
    
      // (B) drei Elemente hinzufügen
      vec.push_back(CSamplePtr(new CSample("A"));
      vec.push_back(CSamplePtr(new CSample("B"));
      vec.push_back(CSamplePtr(new CSample("C"));
    
      // (C) [kor]Einen Zeiger auf dem zweiten[/kor] Element "behalten": 
      CSamplePtr anElement = vec[1];
    
      // (D) den Vektor freigeben:
      vec.clear();  // "A" und "C" werden hier freigegeben
    
      // (E) das zweite Element "B" existiert noch: 
      anElement->Use();
      printf("fertig.\n");
    
      // (F) anElement verlässt den Gültigkeitsbereich und CSample("C") wird freigegeben
    }
    

    _________________________________________________________________________________

    shared_ptr<T> - Die wichtigsten Eigenschaften

    Es gibt unzählige Smart Pointer-Implementationen. Einige wichtige Eigenschaften machen boost::shared_ptr empfehlenswert:

    • shared_ptr<T> kann für einen unvollständigen Typ T verwendet werden:
      class X; // forward declaration
      shared_ptr<X> pX; // "X *"
      Zum Dereferenzieren und Freigeben von pX muss X aber vollständig bekannt sein - boost prüft das und wirft andernfalls eine Compile-Fehlermeldung.
    • shared_ptr<T> kann man auf einen beliebigen Typen T anwenden:
      Es gibt praktisch keine Anforderungen an den Typen T (T muss z.B. nicht von einer vorgegebenen Basisklasse abgeleitet sein)
    • shared_ptr<T> erlaubt einen Custom Deleter
      Die automatische Freigabe des referenzierten Objekts erfolgt normalerweise mit delete. Man kann aber auch einen anderen Funktor angeben. Dadurch lassen sich beliebige Ressourcen in einem shared_ptr verwalten.
    • Der Deleter ist kein Template-Parameter der Klasse - dadurch wird man nicht zu weiteren Templates gezwungen (man kann void foo(shared_ptr<int>) statt template <typename D> void foo(shared_ptr<int,D>) verwenden)
    • Implizite Konvertierung:
      Wenn U * implizit in T * umgewandelt werden kann (z.B., weil T eine Basisklasse von U ist), kann ein shared_ptr<U> auch implizit in einen shared_ptr<T> umgewandelt werden.
    • shared_ptr ist threadsicher
      (Das ist zwar eher eine Entwurfsentscheidung als ein uneingeschränkter Vorteil, für Multithread-Programme aber unumgänglich und mit nur geringen Performance-Einbußen erkauft)
    • portabel, peer-reviewed, das Übliche.

    _________________________________________________________________________________

    Was kann schief gehen?

    Zwar sind Smart Pointer um einiges robuster als gewöhnliche Zeiger, aber wie immer gibt es einige Dinge, die man unbedingt wissen muss:

    Regel 1: Zuweisen und Halten - Ein mit new erzeugtes Objekt sollte sofort an einen Smart Pointer zugewiesen und ausschließlich über diese referenziert werden. Die Smart Pointer sind nun Eigentümer des Objekts. Dadurch vermeidet man versehentliches vorzeitiges Löschen

    Regel 2: Ein _ptr<T> ist kein T * - genauer gesagt: es gibt keine implizite Konvertierung zwischen einem T * und einem shared_ptr<T>. Dadurch wird eine versehentliche Verletzung von Regel (1) ausgeschlossen. "Gefährliche" Operationen müssen daher explizit gemacht werden:

    • Ein Smart Pointer wird normalerweise mit ..._ptr<T> myPtr(new T) erzeugt.
    • Ein T * kann nicht an einen Smart Pointer zugewiesen werden.
    • Nicht einmal ptr=NULL ist erlaubt - stattdessen verwendet man ptr.reset().
    • Der normale Zeiger ist über ptr.get() verfügbar***. Natürlich ist dieser Zeiger nur gültig, solange der entsprechende Smart Pointer existiert, und Freigeben per delete wäre fatal.***
    • An eine Funktion, die einen _ptr<T> erwartet, kann ein T * nicht implizit übergeben werden. Stattdessen erstellt man einen lokalen Smart Pointer - damit gibt man den "Besitz" explizit ab. (Achtung! Regel 3!)
    • Es gibt keine allgemein gültige Methode, den Smart Pointer, der einen bestimmten normalen Zeiger enthält, aufzufinden, jedoch stellen die Boost: Smart Pointer Programming Techniques Lösungen für häufige Situationen dar.

    Regel 3: keine temporären shared_ptr - immer schön eine Variable vergeben. (Notwendig für korrekte Exception-Behandlung, Details findet man unter boost: shared_ptr best practices)

    Regel 4: Keine zirkulären Referenzen - mehr dazu sofort.
    Generell sind Boost Smart Pointer auf Sicherheit getrimmt - potenziell gefährliche Operationen müssen explizit geschrieben werden.
    _________________________________________________________________________________

    Zyklische Referenzen

    Referenzzählung erlaubt bequemes und effektives automatisches Ressourcenmanagement, hat aber eine Schwachstelle: Objekte, die sich (direkt oder indirekt) gegenseitig referenzieren. Ein einfaches Beispiel:

    struct CDad;
    struct CChild;
    
    typedef boost::shared_ptr<CDad>   CDadPtr;
    typedef boost::shared_ptr<CChild> CChildPtr;
    
    struct CDad   : public CSample { CChildPtr myBoy;  };
    struct CChild : public CSample { CDadPtr myDad;    };
    
    CDadPtr   parent(new CDadPtr); 
    CChildPtr child(new CChildPtr);
    
    // absichtlich eine zirkuläre Referenz erstellen:
    parent->myBoy = child; 
    child->myDad = dad;
    
    // einen Zeiger zurücksetzen...
    child.reset();
    

    Sowohl die CChild-Instanz als auch parent referenzieren die CDad-Instanz, welche wiederum die CChild-instanz referenziert:

    Wenn man jetzt dad.reset() aufruft, verlieren die beiden Objekte jeglichen Kontakt zur Außenwelt, halten sich aber gegenseitig am Leben - ein klassisches Leck! Im schlimmsten Fall werden dadurch kritische Ressourcen nicht mehr freigegeben.

    Diese Problem ist nicht (bzw. nur mit inakzeptablen Restriktionen) innerhalb der shared_ptr-Implementation lösbar - man muss den Kreis erkennen und selbst auflösen. Dazu gibt es verschiedene Wege:

    • Bevor man die letzte Referenz freigibt, wird der Kreis manuell aufgelöst (z.B. dad.ReleaseChild(); dad.reset(); ).
    • Wenn Dad eine längere Lebensdauer als Child hat, kann CChild einen gewöhnlichen CDad * verwenden
    • man verwendet boost::weak_ptr für CChild.myDad

    Die Lösungen (1) und (2) können nicht immer eingesetzt werden, funktionieren aber mit allen Smart Pointer-Bibliotheken. (3) ist eigentlich eine Verallgemeinerung von (2), die aber ohne eine Anforderung an die Lebensdauer auskommt.
    _________________________________________________________________________________

    Zyklische Referenzen mit weak_ptr aufbrechen

    Man unterscheidet starke und schwache Referenzen: Eine starke Referenz verhindert das Löschen eines Objekts, eine schwache Referenz tut dies nicht.
    In diesem Sinne ist boost::shared_ptr<T> eine starke und T * eine schwache Referenz. Mit T * kann man aber nicht prüfen, ob das Objekt noch existiert (daher die Lebensdauer-Anforderung in Lösung (2) oben).

    boost::weak_ptr<T> ist ein Smart Pointer, der eine schwache Referenz implementiert. Bei Bedarf kann man sich von einer solchen eine starke Referenz geben lassen. Falls das Objekt nicht mehr existiert, ist diese 0. Die starke Referenz darf natürlich nur solange wie nötig behalten werden.
    Obiges Beispiel mit eine weak_ptr:

    struct CBetterChild : public CSample
    {
      weak_ptr<CDad> myDad;
    
      void BringBeer()
      {
        shared_ptr<CDad> strongDad = myDad.lock(); // eine starke Referenz beantragen
        if (strongDad)                      // ist das Objekt noch existent?
          strongDad->SetBeer();
        // strongDad wird freigegeben, sobald sein Gültigkeitsbereich verlassen wird; die schwache Referenz wird behalten.
      }
    };
    

    Komplexe Objektstrukturen erfordern immer noch eine sorgfältige Analyse, an welchen Stellen zirkuläre Referenzen auftreten und welche davon durch einen weak_ptr aufgebrochen werden können (im Beispiel kann man z.B. nicht den Zeiger auf CChild durch einen weak_ptr ersetzen!) Hat man aber einmal eine ordentliche Struktur aufgebaut, muss man sich um die Freigabe keinen Kopf mehr machen - und gerade bei komplexen Strukturen ist das sehr angenehm.

    _________________________________________________________________________________

    intrusive_ptr<t> - Das Leichtgewicht

    shared_ptr bietet eine Menge mehr als einen "normalen" Zeiger. Das hat natürlich einen kleine Preis: ***er ist größer, ein wenig langsamer, und benötigt zu jedem referenzierten Objekt ein separates "Tracking"-Objekt mit Zähler und Deleter. Das kann man fast immer vernachlässigen.

    Wenn nicht, bietet boost::intrusive_ptr*** eine interessante Alternative: Den "leichtmöglichsten" referenzgezählten Zeiger - allerdings muss man sich um das Zählen selbst kümmern. Das ist gar nicht so schlimm: will man seine eigenen Klassen Smart Pointer-kompatibel machen, packt man den Referenzzähler gleich mit rein. Dafür bekommt man weniger Allokationen und einen fixen Smart Pointer.

    Um einen Typ intrusive_ptr<T> zu verwenden, muss man zwei Funktionen definieren: intrusive_ptr_add_ref und intrusive_ptr_release. Das folgende Beispiel zeigt, wie:

    #include "boost/intrusive_ptr.hpp"
    
    class CRefCounted
    {
      private:
        long    references;
        friend void intrusive_ptr_add_ref(T * p);
        friend void intrusive_ptr_release(T * p);
    
      public:
        CRefCount() : references(0) {}   // references mit 0 initialisieren
    };
    
    // die zwei Funktionsüberladungen müssen bei den meisten Compilern im boost-Namespace stehen:
    namespace boost  
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        // Referenzzähler für das Objekt *p inkrementieren
        ++(p->references);
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       // Referenzzähler dekrementieren und, wenn der Zähler 0 erreicht hat, Objekt löschen
       if (--(p->references) == 0)
         delete p;
      } 
    } // namespace boost
    

    Das ist die einfachste (und nicht threadsichere!) Implementation. Eine generische Implementation findet man nach kurzem Stöbern in der Boost Mailing List.

    Tipp:
    Um das Beispiel unter Windows/VC threadsicher zu bekommen, verwendet man Folgendes:

    extern "C" _InterlockedIncrement(LPLONG lpAddend);
    extern "C" _InterlockedDecrement(LPLONG lpAddend);
    #pragma intrinsic(_InterlockedIncrement, _InterlockedDecrement)  // werden inline expandiert!
    
    namespace boost
    {
      void intrusive_ptr_add_ref(CRefCounted * p)
      {
        _InterlockedIncrement(&(p->references));
      }
    
      void intrusive_ptr_release(CRefCounted * p)
      {
       if (_InterlockedDecrement(&(p->references)) == 0)
         delete p;
      } 
    }
    

    _________________________________________________________________________________

    scoped_array und shared_array

    Die beiden sollen nicht unerwähnt bleiben - sie sind einem scoped_ptr bzw. shared_ptr sehr ähnlich, Syntax und Verhalten ähnelt aber einem mit new[] allozierten Zeiger (z.B. operator[], delete[] als Standard-Deleter). Hinweis: Keiner von beiden merkt sich die Länge, sie sind also als "Array-Ersatz" nur bedingt geeignet.

    _________________________________________________________________________________

    Boost installieren

    Die aktuellen Boost-Bibliotheken können von boost.org heruntergeladen werden. Für die Verwendung der Smart Pointer-Bibliothek ist keine Übersetzung notwendig.
    Das Boost-Wurzelverzeichnis enthält Release Notes und eine Menge Verzeichnisse für Dokumentation, Build Tools usw. Die eigentlichen Quellen befinden sich in dem Unterverzeichnis boost\.

    Ich füge das boost-Wurzelverzeichnis zu den Standard-Include-Pfaden hinzu.
    in VC6: Extras/Einstellungen, "Verzeichnisse"-Reiter, "Verzeichnisse für... Include-Dateien"
    in VC7: Extras/Einstellungen, Projekte/VC++-Verzeichnisse, "Verzeichnisse für... Include-Dateien"

    Die #includes sehen dann so aus:

    #include <boost/smart_ptr.hpp>
    

    _________________________________________________________________________________

    Was ist mit std::auto_ptr?

    std::auto_ptr ist der einzige Smart Pointer im aktuellen Standard. Leider - denn auto_ptr erfüllt zwar seine Zweck, ist aber unnötig komplex und wird schnell falsch eingesetzt.

    auto_ptr in zwei Sätzen: Die letzte auto_ptr-Instanz, der der Zeiger zugewiesen wurde, gibt das referenzierte Objekt frei. Das ist aber nicht notwendigerweise die letzte vorhandene auto_ptr-Instanz.

    In den beiden häufigsten Anwendungsfällen kann und sollte man auf andere Smart Pointer ausweichen:

    1) lokale Objekte / PIMPL ==> scoped_ptr

    class CBar;
    class CFoo
    {
       auto_ptr<CBar>   m_pBar;  // besser: scoped_ptr
       // ...
    }
    

    scoped_ptr verbietet die bei auto_ptr mögliche Zuweisung (die einen u.U. gefährlichen, weil versteckten Transfer of Ownership bedeutet)

    2) Rückgabewert aus Factory ==> shared_ptr

    class CBar;
    auto_ptr<CBar> MakeBar() { ... }  // besser: shared_ptr
    

    shared_ptr erlaubt die hier notwendige Zuweisung - und ist auch bei Mehrfachzuweisungen sicher. Das folgende Beispiel soll das illustrieren:

    auto_ptr<CFont> GetFont(EMyFavoriteFonts emff);
    ...
    auto_ptr<CFont> font = GetFont(emffCoolShadowedFont);
    widget1->font = font;  // ??
    widget2->font = font;  // ??
    

    Wer gibt das CFont-Objekt wieder frei? widget2 - selbst wenn widget1 noch existiert. Hier ist ein Referenzzähler sinnvoll - also verwendet man besser shared_ptr oder intrusive_ptr.

    _________________________________________________________________________________

    Links (alle in Englisch)

    Boost Smart Pointer Bibliothek
    Smart Pointer - Tipps & Tricks
    Herb Sutter über Smart Pointers
    Originaler Artikel
    Kreativer Missbrauch von shared_ptr<T>

    _________________________________________________________________________________

    Sept 05, 2004: Erste Version
    Aug. 2005: Deutsche Übersetzung



  • 👍
    Vor allem mit den Bildern ist das noch besser nachvollziehbar.



  • Peterchen? Wann erstellst du den zu veröffentlichenden Thread und dein Profil?



  • geschafft! Die Verzögerung tut mir leid, aber Artikel zu schreiben ist immer noch einfacher als über mich...

    Wenn ich es recht verstehe, errstelle ich jetzt noch einen neuen Thread, die letzte version des Artikels (abzüglich der [ kor ] - tags) enthält?



  • Kein Problem, so haben wir gleich den ersten für Februar. 🙂
    Ja, das hast du richtig verstanden. Übernimm alle Korrekturen, guck nach eventuellen Anmerkungen und dann mach einen neuen Thread mit [E] und editiere diesen zu [X].


Anmelden zum Antworten