#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;
}Kreis: 3.14159 Rechteck: 12
class 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;
}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;
}Fahrzeug(VW) Auto Fahrzeug VW mit 4 Türen ~Auto ~Fahrzeug
Konstruiert 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;
}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;
}A kein B
Viele 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
Aufgabe
Entwirf ein Interface Zahlung mit betrag() und beschreibung(), zwei Umsetzungen und summiere die Beträge einer std::vector<std::unique_ptr<Zahlung>>.