Das Problem, das Smart Pointer lösen
Auf der vorigen Seite hast du Speicher mit new alloziert und mit delete freigegeben. Das funktioniert, legt aber die Last auf dich: Jedes new braucht ein passendes delete, auf jedem Codepfad, einschließlich derjenigen, auf denen mittendrin eine Ausnahme geworfen wird. Vergisst du eines, leckst du Speicher; führst du delete zweimal aus, beschädigst du den Heap.
Smart Pointer beheben das, indem sie die Lebensdauer von Heap-Speicher an ein normales Objekt auf dem Stack binden. Wenn dieses Objekt seinen Gültigkeitsbereich verlässt, führt sein Destruktor delete für dich aus – garantiert, selbst wenn eine Ausnahme den Stack abwickelt. Diese Idee heißt RAII (Resource Acquisition Is Initialization), und Smart Pointer befinden sich im Header <memory>.
Du verwendest *p und p->member genau so, wie du es bei einem rohen Zeiger tun würdest. Der Unterschied ist, dass du nie delete aufrufst – das erledigt der Smart Pointer.
unique_ptr: Ein Besitzer, kein Teilen
unique_ptr ist der Smart Pointer, zu dem du standardmäßig greifen solltest. Er steht für exklusiven Besitz: Genau ein unique_ptr besitzt das Objekt zu einem Zeitpunkt, und wenn dieser Zeiger stirbt, stirbt das Objekt mit ihm. Er hat im Vergleich zu einem rohen Zeiger keinerlei Laufzeit-Overhead.
Erstelle einen mit make_unique (C++14). Es nimmt die Konstruktorargumente entgegen und gibt dir einen einsatzbereiten Zeiger zurück:
Da es nur einen Besitzer geben kann, kann ein unique_ptr nicht kopiert werden. Der Versuch, ihn zu kopieren, ist ein Kompilierfehler – und dieser Fehler ist die Sprache, die dich davor schützt, dass zwei Besitzer beide dasselbe Objekt mit delete freigeben wollen:
auto a = make_unique<int>(10);
auto b = a; // error: call to deleted copy constructor of unique_ptr
Um den Besitz an jemand anderen zu übergeben, verschiebst du ihn mit std::move. Nach dem Verschieben ist der ursprüngliche Zeiger leer (er enthält nullptr):
Das ist das Modell, das du die meiste Zeit haben willst: Es gibt immer genau einen klaren Besitzer, und der Compiler setzt das durch.
shared_ptr: Gemeinsamer Besitz per Referenzzählung
Manchmal müssen mehrere Teile deines Programms wirklich dasselbe Objekt teilen, und keiner weiß, welcher zuletzt fertig wird. Dafür ist shared_ptr da. Er führt einen Referenzzähler: Jede Kopie erhöht den Zähler, jede Zerstörung verringert ihn, und das Objekt wird erst freigegeben, wenn der Zähler null erreicht.
Erstelle sie mit make_shared:
Anders als bei unique_ptr ist das Kopieren eines shared_ptr in Ordnung – das ist ja gerade der Sinn. Der Kompromiss sind die Kosten: Der Referenzzähler wird auf dem Heap gespeichert und atomar (threadsicher) aktualisiert, daher ist shared_ptr schwerer als unique_ptr. Greife nur dann dazu, wenn der Besitz wirklich geteilt ist, und nicht bloß, um nicht darüber nachdenken zu müssen, wem was gehört.
make_shared ist außerdem effizienter als shared_ptr<T>(new T(...)): Es alloziert das Objekt und den Kontrollblock in einer einzigen Allokation statt in zwei.
weak_ptr und das Auflösen von Referenzzyklen
shared_ptr hat eine klassische Falle: Wenn zwei Objekte shared_ptr aufeinander halten, erreichen ihre Referenzzähler nie null, also wird keines je freigegeben – ein Speicherleck, obwohl du Smart Pointer verwendet hast.
struct Node {
shared_ptr<Node> next; // wenn zwei Knoten aufeinander zeigen,
}; // halten sie sich für immer gegenseitig am Leben
Die Lösung ist weak_ptr: ein nicht besitzender Beobachter eines shared_ptr. Er erhöht den Referenzzähler nicht, hält also nie ein Objekt am Leben. Um das Objekt zu verwenden, rufst du .lock() auf, was dir einen shared_ptr gibt, falls das Objekt noch existiert, oder einen leeren, falls es bereits weg ist.
Verwende weak_ptr für „Rückzeiger" und Caches – überall, wo du ein Objekt referenzieren willst, ohne seinen Besitz zu beanspruchen.
Häufige Fehler und Fallstricke
Smart Pointer beseitigen die meisten Speicherfehler, aber ein paar Fallen bleiben:
Vermische nicht smarten und rohen Besitz desselben Speichers. Erstelle niemals zwei Smart Pointer aus demselben rohen Zeiger – jeder wird versuchen, ihn mit delete freizugeben:
int* raw = new int(5);
unique_ptr<int> a(raw);
unique_ptr<int> b(raw); // Katastrophe: beide löschen denselben int (double free)
Genau deshalb bevorzugst du make_unique/make_shared – es gibt keinen losen rohen Zeiger, der missbraucht werden könnte.
Ein unique_ptr ist nur verschiebbar, also übergib ihn per Wert, um den Besitz zu übertragen. Wenn eine Funktion das Objekt verwenden, aber nicht besitzen soll, nimm stattdessen eine einfache Referenz oder einen rohen T* – ein roher Zeiger, der lediglich beobachtet, ist vollkommen in Ordnung:
void consume(unique_ptr<int> p); // übernimmt den Besitz (hineinverschieben)
void observe(int* p); // schaut nur, besitzt nichts
Greife nicht standardmäßig zu shared_ptr. Es ist verlockend, weil er sich frei kopieren lässt, aber die atomare Referenzzählung kostet echte Performance, und gemeinsamer Besitz macht Lebensdauern schwerer nachvollziehbar. Wähle unique_ptr als Standard; steige nur dann auf shared_ptr um, wenn du tatsächlich mehrere Besitzer brauchst.
unique_ptr für Arrays braucht die Array-Form. make_unique<int[]>(n) gibt dir einen unique_ptr<int[]>, der korrekt delete[] aufruft. In der Praxis bevorzuge std::vector für dynamische Arrays – er verwaltet den Speicher für dich und gibt dir obendrein eine Größenverfolgung.
Als Nächstes: Strings
Du hast die Speicherverwaltung jetzt im Griff: Smart Pointer geben dir Heap-Allokation ohne die Lecks. Eines der häufigsten Dinge, die du allozieren und herumreichen wirst, ist Text – und C++ gibt dir ein weitaus sichereres Werkzeug als rohe char*-Puffer. Die nächste Seite behandelt std::string: wie er von selbst wächst, die Operationen, die du täglich verwenden wirst, und warum er dich vollständig von der manuellen Speicherarbeit befreit.
Häufig gestellte Fragen
Was sind Smart Pointer in C++?
Smart Pointer sind Objekte aus <memory> (unique_ptr, shared_ptr, weak_ptr), die einen rohen Zeiger kapseln und den Speicher automatisch mit delete freigeben, sobald sie ihren Gültigkeitsbereich verlassen. Sie geben dir Heap-Allokation ohne das manuelle delete und ohne die Lecks, die durch das Vergessen entstehen.
Was ist der Unterschied zwischen unique_ptr und shared_ptr?
unique_ptr ist der alleinige Besitzer seines Objekts – er kann nicht kopiert, nur verschoben werden, und er gibt den Speicher in dem Moment frei, in dem er stirbt. shared_ptr erlaubt gemeinsamen Besitz über eine Referenzzählung: Viele shared_ptr können auf dasselbe Objekt zeigen, und das Objekt wird erst freigegeben, wenn der letzte zerstört wird. Bevorzuge unique_ptr, sofern du nicht wirklich gemeinsamen Besitz brauchst.
Sollte ich in modernem C++ make_unique oder new verwenden?
Verwende make_unique und make_shared. Sie allozieren und kapseln das Objekt in einem Schritt, sodass es kein rohes new gibt, dessen Ergebnis lecken könnte, bevor es einen Smart Pointer erreicht. Als Faustregel sollte eine moderne C++-Codebasis fast keine nackten new oder delete enthalten.