C++-Programmierung/ Speicherverwaltung/ Smart Pointer

Aus Wikibooks


Als smart guy wird auf Englisch jemand bezeichnet, der vornehm und gebildet ist. In Analogie zu diesem Begriff versteht man unter einem smart pointer ein Objekt, das sich wie ein Zeiger verhält, d.h. es muss Zeigeroperationen wie die Dereferenzierung mit * oder den indirekten Zugriff mit -> unterstützen. Zusätzlich zu diesen Eigenschaften geht der Smart pointer besser mit den Ressourcen um. Konkret bedeutet das, dass er darauf aufpasst, kein Speicherleck entstehen zu lassen.

Das einfachste Beispiel eines Smart pointers ist der in der C++-Bibliothek inkludierte auto_ptr, der in der Header-Datei von <memory> definiert wird. Hier ein Einblick in die Implementation des Auto pointers:

template <typename T>
class auto_ptr{
    T* ptr;
public:
    explicit auto_ptr(T* p = 0) : ptr(p) {}
    ~auto_ptr()                 { delete ptr; }
    T& operator*()              { return *ptr; }
    T* operator->()             { return ptr; }
    // ...
};

Man sieht, dass auto_ptr praktisch eine Hülle um einen primitiven Zeiger darstellt. Wichtig ist, dass der Destruktor den Speicher des Zeigers freigibt, den die Klasse als privaten Member beinhaltet. Weil der Destruktor eines Objekts automatisch aufgerufen wird beim Verlassen des Gültigkeitsbereichs (z. B. der Methode), kann ein delete nicht mehr vergessen werden. Nachteile: Die Methode kann das Objekt, auf das ptr verweist, nicht als Ergebnis zurückgeben - es wird ja automatisch freigegeben. Außerdem kann der belegte Speicher nicht vorzeitig freigegeben werden - außer, man ruft explizit den Destructor auf. In Folge kann danach nicht mehr auf den auto_ptr zugegriffen werden - das Problem des dangling pointers ist in diesem Fall verlagert zu einem ungültigen Zugriff(sversuch) auf ein bereits 'destructed' Objekt.

Beispiele[Bearbeiten]

Verwendet man Smart pointers, wird an Stelle des Codes

void foo(){
    MyClass* p = new MyClass();

    p->DoSomething();

    delete p;
}

die kürzere Form

void foo(){
    auto_ptr<MyClass> p(new MyClass);

    p->DoSomething();
}

verwendet, und man kann darauf vertrauen, dass der Speicher, auf den der Zeiger p verweist, am Ende der Funktion wieder freigegeben wird.

Smart Pointer mit Referenzzählung[Bearbeiten]

Einen Nachteil hat auto_ptr jedoch. Es darf niemals mehr als ein auto_ptr auf den von ihm verwalteten Speicher zeigen. Diese Limitation zeigt sich z.B. beim Kopieren und Zuweisen:

void foo() {
    auto_ptr<MyClass> p(new MyClass);
    auto_ptr<MyClass> p2(p);
    p = p2;
}

In Zeile 3 wird der Kopierkonstruktor aufgerufen - dieser ist für auto_ptr jedoch so definiert, dass er nach dem Kopieren den internen Zeiger der Vorlage (hier p.ptr) auf null setzt. p2 zeigt nun auf das Objekt, p hingegen ist nun null. Zeile 4 hat die gleichen Auswirkungen (nur umgekehrt - jetzt enthält wieder p das Objekt und p2.ptr ist null).

Eine mögliche Lösung zu diesem Problem ist, Smart Pointer zu benutzen, die anders mit dem Speicher umgehen und somit mehrere Zeiger auf den gleichen Speicher erlauben. Die Standardbibliothek bietet für diesen Zweck den Smart Pointer shared_ptr im Namensraum std::tr1 an. Die Speicherverwaltung bei shared_ptr erfolgt über Referenzzählung. Ein Zähler hält fest, wie viele Objekte auf den Speicher zeigen. Fällt der Zähler auf 0, wird der verwaltete Speicher freigegeben. Die Benutzung erfolgt ganz wie bei auto_ptr:

void foo(){
    tr1::shared_ptr<MyClass> p(new MyClass);
    tr1::shared_ptr<MyClass> p2(p);
}

Die Anweisung in Zeile 3 liefert nun eine Kopie in p2, ohne p auf null zu setzen.