Wird geladen...

Einstieg in Smart Pointer

Grundlagen von unique_ptr, shared_ptr und weak_ptr für sichere Objektlebensdauer.

In C++ wird die dynamische Speicherverwaltung traditionell mit new und delete durchgeführt. Die manuelle Speicherverwaltung kann jedoch zu Problemen wie Speicherlecks, doppeltem Freigeben oder hängenden Zeigern (dangling pointers) führen. Modernes C++ (ab C++11) hat das Konzept der Smart Pointer eingeführt, um diese Probleme zu lösen. Smart Pointer automatisieren die Speicherverwaltung und beseitigen viele dieser Fehler.


1. Was ist ein Smart Pointer?

Ein Smart Pointer ist eine spezielle C++-Klasse, die die Lebensdauer eines Objekts verwaltet. Er verhält sich wie ein normaler Zeiger, kontrolliert jedoch automatisch die Lebensdauer (Erstellung und Zerstörung) des Objekts. Dadurch werden Speicherlecks verhindert.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> ptr = make_unique<int>(42);
    cout << *ptr << endl; // 42
}

Es gibt drei Haupttypen von Smart Pointern: unique_ptr, shared_ptr und weak_ptr. Alle befinden sich in der Header-Datei <memory>.


2. unique_ptr

std::unique_ptr ist ein Zeiger, dessen Besitz ausschließlich einem Objekt gehört. Zu einem bestimmten Zeitpunkt kann nur ein unique_ptr auf ein bestimmtes Objekt zeigen. Er kann nicht kopiert, aber mit std::move() verschoben werden.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> p1 = make_unique<int>(10);
    cout << *p1 << endl;

    // unique_ptr kann nicht kopiert werden:
    // unique_ptr<int> p2 = p1; // FEHLER!

    // Aber kann verschoben werden:
    unique_ptr<int> p2 = move(p1);
    cout << *p2 << endl;
}

Vorteile:

Nachteil:
unique_ptr kann nicht kopiert werden, daher ist er ungeeignet, wenn mehrere Instanzen das gleiche Objekt teilen sollen.


3. shared_ptr

std::shared_ptr ermöglicht mehreren Zeigern, denselben Besitz eines Objekts zu teilen. Die Lebensdauer des Objekts hängt von der Anzahl der Referenzen (reference count) ab. Wenn der letzte shared_ptr zerstört wird, wird das Objekt automatisch gelöscht.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> p1 = make_shared<int>(100);
    shared_ptr<int> p2 = p1; // Geteilter Besitz

    cout << "p1: " << *p1 << ", p2: " << *p2 << endl;
    cout << "Referenzanzahl: " << p1.use_count() << endl; // 2
}

Vorteile:

Nachteile:


4. weak_ptr

std::weak_ptr ist eine “nicht-besitzende” Referenz auf ein Objekt, das von einem shared_ptr verwaltet wird. Ein weak_ptr verlängert die Lebensdauer des Objekts nicht, sondern beobachtet es nur. Dadurch werden zyklische Referenzen verhindert.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(50);
    weak_ptr<int> wp = sp;

    cout << "Referenzanzahl: " << sp.use_count() << endl;

    if (auto ptr = wp.lock()) {
        cout << "Wert: " << *ptr << endl;
    }
}

wp.lock() gibt einen gültigen shared_ptr zurück, falls das Objekt noch existiert. Wenn das Objekt bereits gelöscht wurde, wird nullptr zurückgegeben. Dies ermöglicht einen sicheren Zugriff.


5. Zyklisches Referenzproblem

Wenn sich zwei shared_ptr-Objekte gegenseitig referenzieren, fällt der Referenzzähler nie auf null, und der Speicher wird nicht freigegeben. Dieses Problem tritt häufig bei verketteten oder baumartigen Strukturen auf.


#include <memory>
using namespace std;

struct Node {
    shared_ptr<Node> next;
    // FEHLER: Zyklische Referenz!
};

int main() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    n1->next = n2;
    n2->next = n1; // Endlose Referenzschleife
}

Die Lösung besteht darin, einen weak_ptr zu verwenden:


struct Node {
    weak_ptr<Node> next; // Keine Referenzschleife mehr
};

6. Verwendung von Smart Pointern in Funktionen

Smart Pointer können wie normale Zeiger an Funktionen übergeben werden. Dabei sollte jedoch das Besitzmodell beachtet werden.


void Drucken(shared_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto ptr = make_shared<int>(77);
    Drucken(ptr); // Sicherer geteilter Zugriff
}

Wenn der Besitz übertragen werden soll, muss unique_ptr mit std::move() verwendet werden.


void Empfangen(unique_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto p = make_unique<int>(88);
    Empfangen(move(p)); // p ist jetzt nullptr
}

7. Vorteile der Speicherverwaltung mit Smart Pointern


8. Vergleichstabelle

Typ Besitz Kopierbar Referenzzählung Verwendungszweck
unique_ptr Einzeln Nein Nein Objekte mit einem Besitzer
shared_ptr Geteilt Ja Ja Objekte mit mehreren Besitzern
weak_ptr Kein Besitz Ja Abhängig von shared_ptr Vermeidung zyklischer Referenzen

9. TL;DR

  • unique_ptr → Einzelliger Besitz, nicht kopierbar, verschiebbar.
  • shared_ptr → Geteilter Besitz mit Referenzzählung.
  • weak_ptr → Beobachtet ein Objekt, ohne es zu besitzen.
  • Smart Pointer verhindern weitgehend Speicherlecks.
  • Bei zyklischen Referenzen sollte weak_ptr verwendet werden.
  • Alle Beispiele können mit Visual Studio 2022 oder GCC 11+ kompiliert werden.

Ähnliche Artikel