EMZETT.
Login

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++

Kapitel 7 von 15 im Kurs C++. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

#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: 12
  • class B : public A erbt öffentlich von A
  • virtual erlaubt, dass die Methode in Unterklassen überschrieben wird und trotzdem über einen Basiszeiger die richtige Version läuft
  • = 0 macht die Methode rein virtuell: Die Klasse ist abstrakt und nicht instanziierbar
  • override lä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
~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;
}

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 B

Viele dynamic_cast sind ein Zeichen für schlechten Entwurf: Besser ist eine virtuelle Methode.

Merke

  • virtual + override fü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

Weiter im Kurs

Zurück: Klassen und Objekte

Weiter: Speicherverwaltung und Smart Pointer

Alle Kapitel: C++ im Überblick