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:
- Automatische Speicherverwaltung
- Leichtgewichtig und performant
- Kein manuelles
deleteerforderlich
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:
- Mehrere Zeiger können dasselbe Objekt besitzen
- Kein manuelles
deletenotwendig
- Erhöhter Speicherverbrauch und Verwaltungsaufwand durch Referenzzählung
- Kann bei zyklischen Referenzen (circular references) zu Speicherlecks führen
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
- Kein manuelles
deleteerforderlich - Verhindert Speicherlecks
- Implementiert das RAII-Prinzip (Resource Acquisition Is Initialization)
- Automatische Speicherfreigabe bei Ausnahmen
weak_ptrvermeidet zyklische Referenzen
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_ptrverwendet werden. - Alle Beispiele können mit Visual Studio 2022 oder GCC 11+ kompiliert werden.