In C++ gehört jede Ressource einem Besitzer. Modernes C++ löst das über Objekte und Smart Pointer (<memory>), nicht über manuelles new/delete.
Stack und Heap
- Stack: lokale Variablen, automatisch, schnell, begrenzt
- Heap: dynamisch reservierter Speicher (
new), muss freigegeben werden
#include <iostream>
int main() {
int a = 5; // Stack
int* p = new int(7); // Heap (roh!)
std::cout << a << ' ' << *p << '\n';
delete p; // Pflicht, sonst Speicherleck
p = nullptr;
return 0;
}5 7
Das funktioniert, ist aber fehleranfällig: vergessenes delete, doppeltes delete, Ausnahmen zwischen new und delete. Deshalb: kein rohes `new`/`delete` im Alltag.
unique_ptr: ein Besitzer
std::unique_ptr<T> besitzt ein Objekt allein und löscht es automatisch. Er ist nicht kopierbar, nur verschiebbar:
#include <iostream>
#include <memory>
#include <string>
struct Konto {
std::string name;
explicit Konto(std::string n) : name(std::move(n)) { std::cout << "erzeugt " << name << '\n'; }
~Konto() { std::cout << "gelöscht " << name << '\n'; }
};
std::unique_ptr<Konto> erzeuge(const std::string& n) {
return std::make_unique<Konto>(n); // Besitz wird zurückgegeben
}
int main() {
auto a = erzeuge("A");
{
auto b = std::make_unique<Konto>("B");
std::cout << b->name << '\n';
} // B wird hier automatisch gelöscht
auto c = std::move(a); // Besitz wandert zu c
std::cout << (a == nullptr) << ' ' << c->name << '\n';
return 0;
}erzeugt A erzeugt B B gelöscht B 1 A gelöscht A
std::make_unique<T>(args...) ist der richtige Weg, ein Objekt zu erzeugen.
shared_ptr: gemeinsamer Besitz
std::shared_ptr<T> zählt, wie viele Besitzer es gibt, und löscht das Objekt, wenn der letzte weg ist:
#include <iostream>
#include <memory>
struct Bild { ~Bild() { std::cout << "Bild gelöscht\n"; } };
int main() {
std::shared_ptr<Bild> a = std::make_shared<Bild>();
std::cout << a.use_count() << '\n';
{
std::shared_ptr<Bild> b = a; // zweiter Besitzer
std::cout << a.use_count() << '\n';
}
std::cout << a.use_count() << '\n';
a.reset(); // letzter Besitzer gibt frei
std::cout << "ende\n";
return 0;
}1 2 1 Bild gelöscht ende
std::weak_ptr verweist auf ein shared_ptr-Objekt, ohne Besitzer zu sein. Das bricht Zyklen auf (zwei Objekte, die sich gegenseitig mit shared_ptr halten, würden nie gelöscht).
Welcher Zeiger wann?
| Situation | Wahl |
|---|---|
| Wert reicht | kein Zeiger, einfach Objekt oder std::vector |
| ein klarer Besitzer | std::unique_ptr |
| geteilter Besitz | std::shared_ptr |
| nur beobachten, nicht besitzen | Referenz, roher Zeiger (T*) ohne Besitz, weak_ptr |
| Zahl/Text wachsend | std::vector, std::string |
Ein roher Zeiger (T*) oder eine Referenz bedeutet in modernem C++ immer: "Ich leihe mir das Objekt nur, ich besitze es nicht."
Move-Semantik
Kopieren großer Objekte ist teuer. Beim Verschieben (std::move) wird der Inhalt übertragen, ohne zu kopieren:
#include <iostream>
#include <vector>
#include <string>
#include <utility>
std::vector<int> baue(int n) {
std::vector<int> v;
for (int i = 0; i < n; i++) v.push_back(i);
return v; // wird nicht kopiert: Move bzw. Copy Elision
}
int main() {
std::vector<int> a = baue(1000);
std::vector<int> b = std::move(a); // Besitz des Puffers wandert
std::cout << a.size() << ' ' << b.size() << '\n';
std::string s1 = "ein ziemlich langer Text, der nicht in den kleinen Puffer passt";
std::string s2 = std::move(s1);
std::cout << s1.size() << ' ' << (s2.size() > 10) << '\n';
std::vector<std::string> liste;
std::string s = "Hallo";
liste.push_back(s); // Kopie
liste.push_back(std::move(s)); // Verschieben
liste.emplace_back("direkt"); // direkt im Vektor erzeugen
std::cout << liste.size() << '\n';
return 0;
}0 1000 0 1 3
Gib lokale Objekte einfach per return zurück (nicht std::move darauf schreiben), der Compiler optimiert das.
Typische Fehler und wie man sie vermeidet
| Fehler | Besser |
|---|---|
new ohne delete | std::make_unique, Container |
| Zeiger auf lokale Variable zurückgeben | Wert zurückgeben |
| Dangling Reference (Referenz auf zerstörtes Objekt) | Lebensdauer prüfen, Wert statt Referenz speichern |
Iteratoren nach push_back weiterbenutzen | Iteratoren neu holen |
delete[] vs delete verwechseln | std::vector/std::array |
zirkuläre shared_ptr | weak_ptr |
Werkzeuge
Wie bei C: -fsanitize=address,undefined, Valgrind, clang-tidy und cppcheck finden Speicherfehler früh.
Merke
- Kein rohes
new/delete:std::make_unique,std::make_shared, Container unique_ptr= ein Besitzer,shared_ptr= geteilter Besitz,weak_ptrbeobachtet- Roher Zeiger oder Referenz = Leihgabe ohne Besitz
std::moveüberträgt Ressourcen statt sie zu kopieren- RAII gibt Ressourcen auch bei Ausnahmen frei
Aufgabe
Baue eine einfach verkettete Liste mit std::unique_ptr<Knoten> und füge drei Elemente ein. Wie wird die Liste am Ende freigegeben?