C++ Smart Pointer

6 Kernkonzepte
Die wichtigsten Smart Pointer in C++: unique_ptr · shared_ptr · weak_ptr · make_unique · make_shared · move / reset / get Smart Pointer sind ein zentrales Feature des modernen C++ (C++11 und später). Sie automatisieren die Speicherverwaltung, verhindern Speicherlecks und machen den Code sicherer und lesbarer.

unique_ptr – Exklusiver Besitz

std::unique_ptr<T>
#include <memory> std::unique_ptr<int> p1(new int(42)); auto p2 = std::make_unique<int>(42); // C++14 // p1 ist der exklusive Besitzer – Kopieren nicht möglich

unique_ptr ist ein Smart Pointer mit exklusivem Besitz. Es kann nicht kopiert werden (nur verschoben) und gibt den Speicher automatisch frei, wenn es den Gültigkeitsbereich verlässt.

Wichtige Operationen

Operation Beschreibung
make_unique<T>(args) Erstellt einen unique_ptr (C++14)
ptr.get() Gibt den rohen Zeiger zurück (ohne Besitz)
ptr.reset(p) Setzt den Zeiger neu (löscht alten Speicher)
ptr.release() Gibt den Besitz auf (Rückgabe des rohen Zeigers)
std::move(ptr) Besitz übertragen (Move)
Beispiele
#include <memory>
#include <vector>
// Erstellung
auto p1 = std::make_unique<int>(42);
auto p2 = std::make_unique<std::string>("Hallo");
// Zugriff
std::cout << *p1 << std::endl; // 42
std::cout << p2->size() << std::endl; // 5
// Move (Besitzübertragung)
auto p3 = std::move(p1); // p1 ist jetzt nullptr
// In Container (nur Move möglich)
std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(10));
// vec.push_back(p1); // Fehler (Kopie nicht möglich)
vec.push_back(std::move(p2)); // OK (Move)
// Speicher freigeben
p3.reset(); // Löscht den Speicher
Tipp: Verwenden Sie make_unique (C++14) für die Erstellung von unique_ptr. Es ist sicherer und effizienter als new.

shared_ptr – Geteilter Besitz

std::shared_ptr<T>
#include <memory> auto p1 = std::make_shared<int>(42); auto p2 = p1; // Kopie möglich – Referenzzähler erhöht std::cout << p1.use_count(); // 2

shared_ptr verwendet Referenzzählung – mehrere Pointer können auf dasselbe Objekt zeigen. Der Speicher wird freigegeben, wenn der letzte shared_ptr zerstört wird.

Wichtige Operationen

Operation Beschreibung
make_shared<T>(args) Erstellt einen shared_ptr (effizienter)
ptr.use_count() Gibt die Anzahl der shared_ptr zurück
ptr.unique() Prüft, ob der Besitz exklusiv ist
ptr.get() Gibt den rohen Zeiger zurück
ptr.reset(p) Setzt den Zeiger neu (Referenzzähler sinkt)
Beispiele
// Erstellung
auto p1 = std::make_shared<std::vector<int>>(100);
// Kopie (Referenzzähler erhöht)
auto p2 = p1;
auto p3 = p1;
std::cout << p1.use_count() << std::endl; // 3
// In Containern
std::vector<std::shared_ptr<int>> vec;
vec.push_back(p1); // OK (Kopie)
vec.push_back(p2);
// Speicher freigeben
p2.reset(); // Referenzzähler sinkt
p3.reset(); // Referenzzähler sinkt
// p1 ist jetzt der einzige Besitzer
// Benutzerdefinierter Deleter
auto deleter = [](int* p) { std::cout << "Lösche int"; delete p; };
std::shared_ptr<int> p4(new int(5), deleter);
Tipp: make_shared ist effizienter als shared_ptr<T>(new T(...)), weil Speicher für Objekt und Referenzzähler in einer Allokation reserviert wird.

weak_ptr – Schwache Referenz (Zyklusvermeidung)

std::weak_ptr<T>
// weak_ptr wird von shared_ptr erstellt auto shared = std::make_shared<int>(42); std::weak_ptr<int> weak = shared; // weak erhöht den Referenzzähler NICHT if (auto sp = weak.lock()) { // Sicheren shared_ptr erzeugen // Objekt ist noch gültig }

weak_ptr ist eine schwache Referenz auf ein Objekt, das von einem shared_ptr verwaltet wird. Es verhindert zyklische Referenzen (Speicherlecks) und wird oft in Observer-Patterns verwendet.

Wichtige Operationen

Operation Beschreibung
weak.lock() Erzeugt einen shared_ptr (falls Objekt noch existiert)
weak.expired() Prüft, ob das Objekt bereits gelöscht wurde
weak.use_count() Gibt die Anzahl der shared_ptr zurück
weak.reset() Setzt den weak_ptr zurück
Beispiele
// weak_ptr für zyklische Referenzen
class Node {
public:
std::shared_ptr<Node> next; // Starke Referenz
std::weak_ptr<Node> prev; // Schwache Referenz (verhindert Zyklus)
int data;
};
// Verwendung
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // weak_ptr (kein Zyklus)
// weak_ptr prüfen
if (auto shared = node2->prev.lock()) {
std::cout << "Prev existiert noch" << std::endl;
} else {
std::cout << "Prev wurde gelöscht" << std::endl;
}
Tipp: Verwenden Sie weak_ptr immer dann, wenn Sie zyklische Referenzen vermeiden müssen – z.B. in doppelt verketteten Listen, Bäumen oder Observer-Patterns.

make_unique / make_shared – Erstellung

make_unique · make_shared
// C++14: make_unique auto u = std::make_unique<std::string>("Hallo"); // C++11: make_shared auto s = std::make_shared<std::vector<int>>(10, 42);

make_unique (C++14) und make_shared (C++11) sind die bevorzugten Funktionen zur Erstellung von Smart Pointern. Sie sind sicherer und effizienter als die direkte Verwendung von new.

Beispiele
// make_unique (C++14)
auto p1 = std::make_unique<int>(42);
auto p2 = std::make_unique<std::pair<int, double>>(1, 3.14);
auto p3 = std::make_unique<std::array<int, 5>>();
// make_shared (C++11)
auto s1 = std::make_shared<std::string>("Hallo Welt");
auto s2 = std::make_shared<std::vector<double>>(100, 0.0);
// Mit benutzerdefinierten Typen
struct Person {
std::string name;
int age;
Person(std::string n, int a) : name(n), age(a) {}
};
auto p4 = std::make_unique<Person>("Anna", 30);
auto p5 = std::make_shared<Person>("Ben", 25);
// Für Arrays (C++17)
auto arr1 = std::make_unique<int[]>(10);
auto arr2 = std::make_shared<double[]>(5);
Tipp: Verwenden Sie make_unique und make_shared immer statt new. Sie sind sicherer (keine Memory Leaks) und effizienter (besonders make_shared).

Operationen – get, reset, release, move

get() · reset() · release() · move
// Operationen auf Smart Pointern auto p = std::make_unique<int>(42); int* raw = p.get(); // Rohzeiger (ohne Besitz) p.reset(new int(10)); // Neuer Speicher (alter wird gelöscht) int* raw2 = p.release(); // Besitz aufgeben (p = nullptr)

Operationen auf Smart Pointern ermöglichen den Zugriff auf den rohen Zeiger, das Zurücksetzen und das Übertragen von Besitz.

Beispiele
// get() – rohen Zeiger abrufen
auto p = std::make_shared<std::vector<int>>(5, 1);
std::vector<int>* raw = p.get(); // Rohzeiger
std::cout << raw->size() << std::endl; // 5
// reset() – Speicher neu setzen
p.reset(new std::vector<int>(10, 2));
// p.reset() ohne Argument löscht den Speicher
// release() – Besitz aufgeben (nur unique_ptr)
auto u = std::make_unique<int>(42);
int* rawPtr = u.release(); // Besitz übertragen
// rawPtr muss manuell gelöscht werden
delete rawPtr;
// move – Besitz übertragen
auto u2 = std::make_unique<int>(100);
auto u3 = std::move(u2); // u2 ist jetzt nullptr
// swap – zwei Smart Pointer tauschen
auto a = std::make_shared<int>(1);
auto b = std::make_shared<int>(2);
std::swap(a, b); // a = 2, b = 1
⚠️ Warnung: release() gibt den Besitz auf – der Speicher muss manuell freigegeben werden! Verwenden Sie release() nur, wenn Sie den Besitz an eine andere Komponente übergeben müssen.

Best Practices – Richtige Verwendung

RAII · make · unique · shared · weak
// Best Practice: Smart Pointer richtig einsetzen // 1. unique_ptr für exklusiven Besitz auto u = std::make_unique<MyClass>(args); // 2. shared_ptr für geteilten Besitz auto s = std::make_shared<MyClass>(args); // 3. weak_ptr für zyklische Referenzen std::weak_ptr<MyClass> w = s;

Best Practices für die Verwendung von Smart Pointern – Tipps für sicheren und effizienten Code.

Beispiele
// 1. Verwenden Sie make_unique / make_shared
// Gut: auto p = std::make_unique<T>(args);
// Schlecht: std::unique_ptr<T> p(new T(args));
// 2. unique_ptr für Factory-Funktionen
std::unique_ptr<Widget> createWidget() {
return std::make_unique<Widget>();
}
// 3. shared_ptr für Caching / Shared Ownership
std::shared_ptr<Resource> getResource() {
static auto cache = std::make_shared<Resource>();
return cache;
}
// 4. weak_ptr für Observer / Callbacks
class Observer {
std::weak_ptr<Subject> subject;
void update() {
if (auto s = subject.lock()) {
// Subject existiert noch
}
}
};
// 5. Keine rohen Zeiger für Besitz
// Verwenden Sie unique_ptr/shared_ptr für Besitz
// Verwenden Sie rohe Zeiger nur für Parameter ohne Besitz
void process(Widget* widget) { // OK: kein Besitz
// ...
}
Tipp: Befolgen Sie die RAII-Prinzipien: Ressourcen werden im Konstruktor erworben und im Destruktor freigegeben. Smart Pointer implementieren dieses Prinzip automatisch für dynamischen Speicher.

C++ Smart Pointer im Überblick

unique_ptr Exklusiver Besitz
Nicht kopierbar, nur move
shared_ptr Geteilter Besitz
Referenzzählung
weak_ptr Schwache Referenz
Verhindert Zyklen
make_shared Effiziente Erstellung
Eine Allokation
move Besitz übertragen
std::move
RAII Ressourcenverwaltung
Automatische Freigabe

Quick Summary

unique_ptr
Exklusiver Besitz
shared_ptr
Geteilter Besitz
weak_ptr
Schwache Referenz
make_*
Erstellung
get()
Rohzeiger
RAII
Best Practice
auto u = std::make_unique<T>(args); · auto s = std::make_shared<T>(args); · std::weak_ptr<T> w = s;