Kurz: class Form { public: virtual ~Form() = default; // virtueller Destruktor: Pflicht bei Vererbung virtual double flaeche() const = 0; // rein virtuell: abstrakt virtual std::string name() const { return “Form”.
Teil des Kurses C++
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <cmath>
class Form {
public:
virtual ~Form() = default; // virtueller Destruktor: Pflicht bei Vererbung
virtual double flaeche() const = 0; // rein virtuell: abstrakt
virtual std::string name() const { return "Form"; }
void info() const { std::cout << name() << ": " << flaeche() << '\n'; }
};
class Kreis : public Form {
public:
explicit Kreis(double r) : r_(r) {}
double flaeche() const override { return 3.14159 * r_ * r_; }
std::string name() const override { return "Kreis"; }
private:
double r_;
};
class Rechteck : public Form {
public:
Rechteck(double b, double h) : b_(b), h_(h) {}
double flaeche() const override { return b_ * h_; }
std::string name() const override { return "Rechteck"; }
private:
double b_, h_;
};
int main() {
std::vector<std::unique_ptr<Form>> formen;
formen.push_back(std::make_unique<Kreis>(1.0));
formen.push_back(std::make_unique<Rechteck>(3, 4));
for (const auto& f : formen) f->info(); // Polymorphie: die richtige Version wird gewählt
return 0;
}Ausgabe:
Kreis: 3.14159
Rechteck: 12class B : public Aerbt öffentlich vonAvirtualerlaubt, dass die Methode in Unterklassen überschrieben wird und trotzdem über einen Basiszeiger die richtige Version läuft= 0macht die Methode rein virtuell: Die Klasse ist abstrakt und nicht instanziierbaroverridelässt den Compiler prüfen, dass wirklich überschrieben wird (immer schreiben)- Ein virtueller Destruktor in der Basisklasse ist Pflicht, sonst wird beim Löschen über einen Basiszeiger nur die Basis zerstört
Ohne virtual: Slicing und statische Bindung
#include <iostream>
struct Tier { std::string laut() const { return "..."; } };
struct Hund : Tier { std::string laut() const { return "Wuff"; } };
int main() {
Hund h;
Tier& t = h;
std::cout << h.laut() << ' ' << t.laut() << '\n'; // ohne virtual gilt der statische Typ
Tier kopie = h; // Slicing: der Hund-Teil geht verloren
std::cout << kopie.laut() << '\n';
return 0;
}Ausgabe:
Wuff ...
...Polymorphie funktioniert nur über Zeiger oder Referenzen auf die Basisklasse und nur bei virtual. Wer eine Basisklasse als Wert kopiert, schneidet die abgeleitete Klasse ab (Slicing).
Konstruktoren in der Vererbung
#include <iostream>
#include <string>
class Fahrzeug {
public:
Fahrzeug(std::string marke) : marke_(std::move(marke)) { std::cout << "Fahrzeug(" << marke_ << ")\n"; }
virtual ~Fahrzeug() { std::cout << "~Fahrzeug\n"; }
virtual std::string beschreibung() const { return "Fahrzeug " + marke_; }
protected:
std::string marke_;
};
class Auto : public Fahrzeug {
public:
Auto(std::string marke, int tueren) : Fahrzeug(std::move(marke)), tueren_(tueren) { std::cout << "Auto\n"; }
~Auto() override { std::cout << "~Auto\n"; }
std::string beschreibung() const override { return Fahrzeug::beschreibung() + " mit " + std::to_string(tueren_) + " Türen"; }
private:
int tueren_;
};
int main() {
Auto a("VW", 4);
std::cout << a.beschreibung() << '\n';
return 0;
}Ausgabe:
Fahrzeug(VW)
Auto
Fahrzeug VW mit 4 Türen
~Auto
~FahrzeugKonstruiert wird von der Basis zur abgeleiteten Klasse, zerstört in umgekehrter Reihenfolge.
Mehrfachvererbung und Interfaces
C++ erlaubt mehrere Basisklassen. Üblich und unproblematisch ist die Variante mit reinen Interfaces (nur rein virtuelle Methoden):
#include <iostream>
#include <memory>
struct Fahrbar { virtual ~Fahrbar() = default; virtual void fahren() const = 0; };
struct Ladbar { virtual ~Ladbar() = default; virtual void laden(int prozent) = 0; };
class ElektroAuto : public Fahrbar, public Ladbar {
public:
void fahren() const override { std::cout << "surr, Akku " << akku_ << "%\n"; }
void laden(int p) override { akku_ = std::min(100, akku_ + p); }
private:
int akku_ = 20;
};
int main() {
ElektroAuto e;
e.laden(50);
const Fahrbar& f = e;
f.fahren();
return 0;
}Ausgabe:
surr, Akku 70%Komposition statt Vererbung
Prüfe die Frage: “Ist ein” (Vererbung) oder “hat ein” (Komposition)? Ein Auto hat einen Motor. Vererbung koppelt Klassen eng und lässt sich später schwer ändern. Bevorzuge Komposition und Interfaces.
Typen zur Laufzeit prüfen
#include <iostream>
#include <memory>
struct Basis { virtual ~Basis() = default; };
struct A : Basis { void nurA() const { std::cout << "A\n"; } };
struct B : Basis { };
int main() {
std::unique_ptr<Basis> p = std::make_unique<A>();
if (auto* a = dynamic_cast<A*>(p.get())) a->nurA(); // Sicherer Cast, nullptr wenn falsch
if (dynamic_cast<B*>(p.get()) == nullptr) std::cout << "kein B\n";
return 0;
}Ausgabe:
A
kein BViele dynamic_cast sind ein Zeichen für schlechten Entwurf: Besser ist eine virtuelle Methode.
Merke
virtual+overridefür Polymorphie; virtueller Destruktor in Basisklassen- Rein virtuelle Methoden (
= 0) machen Klassen abstrakt (Interface) - Polymorphie braucht Zeiger/Referenzen; Wertkopien führen zu Slicing
- Konstruktoren laufen Basis → abgeleitet, Destruktoren umgekehrt
- Komposition und Interfaces vor tiefer Vererbung
Übungsaufgabe
Entwirf ein Interface Zahlung mit betrag() und beschreibung(), zwei Umsetzungen und summiere die Beträge einer std::vector<std::unique_ptr<Zahlung>>.
Quiz zur Selbstkontrolle
Wozu dient override?
- Der Compiler prüft, dass wirklich eine virtuelle Methode überschrieben wird (richtig)
- Es macht die Methode virtuell
- Es löscht die Basismethode
- Es ist Pflicht für alle Methoden
Was ist Slicing?
- Beim Kopieren eines abgeleiteten Objekts in die Basisklasse geht der abgeleitete Teil verloren (richtig)
- Das Zerlegen von Strings
- Ein Speicherfehler
- Das Löschen von Objekten
Warum braucht die Basisklasse einen virtuellen Destruktor?
- Damit beim Löschen über einen Basiszeiger das gesamte Objekt zerstört wird (richtig)
- Damit der Code schneller läuft
- Er ist nur Konvention
- Damit Objekte kopierbar sind
Weiter im Kurs
Zurück: Klassen und Objekte
Weiter: Speicherverwaltung und Smart Pointer
Alle Kapitel: C++ im Überblick