Klassen, Pointer & RAII
Klassen in C++, Konstruktor / Destruktor, Speichermodell mit Stack und Heap, Smart Pointers und das fundamentale Prinzip RAII.
Inhaltsverzeichnis
Klassen, Pointer & RAII
Das ist das Kapitel, in dem C++ wirklich eigen wird. RAII (Resource Acquisition Is Initialization) ist das fundamentale C++-Idiom - versteh es, und der Rest wird klar.
Klassen - Basics
class Rechteck {
public:
Rechteck(double breite, double hoehe)
: breite_{breite}, hoehe_{hoehe}
{}
double flaeche() const {
return breite_ * hoehe_;
}
void skalieren(double faktor) {
breite_ *= faktor;
hoehe_ *= faktor;
}
private:
double breite_;
double hoehe_;
};
int main() {
Rechteck r{3.0, 4.0};
std::cout << r.flaeche() << "\n"; // 12
r.skalieren(2.0);
std::cout << r.flaeche() << "\n"; // 48
}
Struktur
public:- von aussen zugreifbarprivate:- nur in der Klasse zugreifbar (Default)protected:- in Klasse und Subklassen_-Suffix fuer private Felder ist Konvention (oder manche nutzenm_als Praefix)
Konstruktor
Rechteck(double breite, double hoehe)
: breite_{breite}, hoehe_{hoehe}
{}
Die Member-Initializer-Liste (: name{wert}, ...) ist effizienter und manchmal noetig.
const-Methoden
double flaeche() const {
return breite_ * hoehe_;
}
const nach der Parameter-Liste bedeutet: “diese Methode aendert das Objekt nicht”. Wichtig, um const-Objekte nutzen zu koennen.
struct vs class
struct Punkt {
double x;
double y;
};
class Rechteck {
// ...
};
Der einzige Unterschied: struct-Member sind public by default, class-Member private by default.
Konvention:
structfuer einfache Datencontainer (wie C-Structs)classfuer komplexere Typen mit Kapselung
Stack vs Heap
Das wichtigste C++-Konzept - wo leben deine Objekte?
Stack (schnell, automatisch)
void funktion() {
Rechteck r{3, 4}; // lebt auf dem Stack
// ...
} // r wird hier automatisch zerstoert
Heap (manuell verwaltet)
void funktion() {
Rechteck* r = new Rechteck{3, 4}; // lebt auf dem Heap
// ...
delete r; // muss manuell freigegeben werden
}
Problem: Wenn du delete vergisst, hast du ein Memory Leak. Wenn du zweimal delete aufrufst, Double-Free. Wenn du r nach delete noch nutzt, Use-After-Free.
RAII - das Heilmittel
RAII (Resource Acquisition Is Initialization) bedeutet:
Ressourcen werden im Konstruktor geholt und im Destruktor freigegeben.
Destruktor heisst ~Klassenname:
class Datei {
public:
Datei(const std::string& pfad) {
f_ = std::fopen(pfad.c_str(), "r");
}
~Datei() {
if (f_) std::fclose(f_); // garantierte Freigabe
}
// Kopieren verbieten - einfacher sicher zu sein
Datei(const Datei&) = delete;
Datei& operator=(const Datei&) = delete;
private:
FILE* f_ = nullptr;
};
void arbeiten() {
Datei d{"daten.txt"};
// ... nutzen
} // Destruktor wird garantiert aufgerufen - Datei wird geschlossen
Das garantiert, dass die Datei geschlossen wird - auch bei Exceptions.
Smart Pointers
Rohe Pointer mit new/delete sind in modernem C++ fast nie noetig. Nutze Smart Pointers aus <memory>:
std::unique_ptr
Exklusiver Besitz - genau ein Owner:
#include <memory>
void arbeiten() {
auto p = std::make_unique<Rechteck>(3.0, 4.0);
std::cout << p->flaeche();
// ... keine delete noetig
} // unique_ptr zerstoert sich, gibt Rechteck frei
Bewegen, aber nicht kopieren:
auto a = std::make_unique<Rechteck>(3, 4);
auto b = std::move(a); // a ist jetzt leer, b hat den Zeiger
std::shared_ptr
Geteilter Besitz - mehrere Owner, Reference Counting:
auto p = std::make_shared<Rechteck>(3, 4);
{
auto q = p; // beide zeigen aufs gleiche Rechteck
std::cout << p.use_count(); // 2
}
std::cout << p.use_count(); // 1 - q ist weg
std::weak_ptr
Nicht-besitzende Referenz, um Zyklen zu vermeiden:
std::weak_ptr<Rechteck> w = p;
if (auto locked = w.lock()) {
// locked ist ein shared_ptr, wenn das Objekt noch lebt
}
Wann welchen?
unique_ptr: der Standard, wenn nur einer Owner istshared_ptr: wenn wirklich mehrere Stellen gleichzeitig besitzenweak_ptr: fuer “weak references” (z.B. Caches, Observer)- Raw Pointer
T*: nur als nicht-besitzende Referenz, oder fuer temporaeren Blick
Move Semantics (kurz)
std::vector<int> a = {1, 2, 3};
std::vector<int> b = std::move(a);
// a ist jetzt in "moved-from"-Zustand - nicht mehr nutzen
// b hat die Daten
std::move uebergibt “Eigentum” - ohne zu kopieren. Das ist bei grossen Objekten ein riesiger Performance-Gewinn.
Vererbung (knapp)
class Tier {
public:
virtual ~Tier() = default;
virtual std::string geraeusch() const = 0; // abstract
};
class Hund : public Tier {
public:
std::string geraeusch() const override {
return "Wuff!";
}
};
void drucke(const Tier& t) {
std::cout << t.geraeusch() << "\n";
}
int main() {
Hund h;
drucke(h); // "Wuff!"
}
Wichtig fuer Vererbung
virtual ~Tier()- virtueller Destruktor, sonst Undefined Behavior beim Polymorphismusvirtualerlaubt Ueberschreibenoverridemacht klar: du ueberschreibst (Compiler prueft)= 0am Ende macht die Methode abstrakt (rein virtuell)
Ein komplettes Beispiel
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Form {
public:
virtual ~Form() = default;
virtual double flaeche() const = 0;
virtual std::string name() const = 0;
};
class Kreis : public Form {
public:
explicit Kreis(double radius) : radius_{radius} {}
double flaeche() const override {
return 3.14159 * radius_ * radius_;
}
std::string name() const override { return "Kreis"; }
private:
double radius_;
};
class Rechteck : public Form {
public:
Rechteck(double breite, double hoehe)
: breite_{breite}, hoehe_{hoehe} {}
double flaeche() const override { return breite_ * hoehe_; }
std::string name() const override { return "Rechteck"; }
private:
double breite_, hoehe_;
};
int main() {
std::vector<std::unique_ptr<Form>> formen;
formen.push_back(std::make_unique<Kreis>(5.0));
formen.push_back(std::make_unique<Rechteck>(3.0, 4.0));
formen.push_back(std::make_unique<Kreis>(2.0));
for (const auto& f : formen) {
std::cout << f->name() << ": " << f->flaeche() << "\n";
}
} // alle Formen werden automatisch freigegeben
Das musst du mitnehmen
- RAII ist das Herzstueck: Konstruktor holt Ressourcen, Destruktor gibt sie frei
- Smart Pointers statt
new/delete- fast immer unique_ptrals Default,shared_ptrnur wenn wirklich noetigconst-Methoden markieren read-onlyvirtualundoverridefuer Polymorphie,virtual ~...()nicht vergessen- Stack ist gratis, Heap braucht Smart Pointer
Zusammenfassung
- Klassen mit Konstruktor, Destruktor, Member-Funktionen, Private/Public
- Stack vs. Heap verstehen
- RAII als Kern-Idiom: Ressourcen an Objekt-Lebenszeit koppeln
- Smart Pointers (
unique_ptr,shared_ptr) statt rohenew/delete - Polymorphie mit
virtual/override const-Methoden fuer “nicht-aendernd”
Damit hast du das Fundament fuer echte C++-Projekte. Im weiteren Kurs gehen wir auf Templates, die Standard-Library, Exception-Handling und Concurrency ein.