Wird geladen...

Mehrfachvererbung und das Diamantproblem in C++

Lernen Sie Mehrfachvererbung und das Diamantproblem in C++, einschließlich virtueller Vererbung und Lösungsansätzen.

C++ unterstützt Mehrfachvererbung (multiple inheritance): Eine Klasse kann von mehr als einer Basisklasse erben. Diese Flexibilität bringt jedoch Komplexität mit sich – insbesondere, wenn dieselbe Basisklasse über verschiedene Vererbungszweige mehrfach geerbt wird. Dies führt zum sogenannten Diamond Problem (Diamantproblem). In diesem Artikel erklären wir, was das Diamond Problem ist, warum es entsteht, welche Symptome es zeigt und wie man es mithilfe von virtueller Vererbung (virtual inheritance) löst.


1. Was ist das Diamond Problem?

Betrachten Sie folgende Hierarchie:


       Publication
        /      \
    EBook    PrintedBook
        \      /
       HybridBook

HybridBook erbt sowohl von EBook als auch von PrintedBook. Da beide wiederum von Publication erben, enthält HybridBook zwei separate Publication-Teilobjekte. Das Ergebnis: Mehrdeutigkeit, Duplikation und zusätzlicher Speicherverbrauch.


2. Problematische (nicht-virtuelle) Vererbung

Sehen wir uns das Problem in der Praxis an:


#include <iostream>
#include <string>
using namespace std;

class Publication {
protected:
    string title;
public:
    Publication(string t) : title(t) {}
    void PrintTitle() const { cout << "Titel: " << title << endl; }
};

class EBook : public Publication {
public:
    EBook(string t) : Publication(t) {}
};

class PrintedBook : public Publication {
public:
    PrintedBook(string t) : Publication(t) {}
};

class HybridBook : public EBook, public PrintedBook {
public:
    HybridBook(string te, string tp) : EBook(te), PrintedBook(tp) {}
};

int main() {
    HybridBook hb("E-Titel", "P-Titel");

    // Mehrdeutigkeit: zwei verschiedene Publication-Teilobjekte
    // hb.PrintTitle(); // FEHLER: 'PrintTitle' ist mehrdeutig

    hb.EBook::PrintTitle();       // Titel: E-Titel
    hb.PrintedBook::PrintTitle(); // Titel: P-Titel
}

Wie man sieht, enthält HybridBook zwei verschiedene Publication-Objekte. Das führt zu Doppelungen und Mehrdeutigkeiten bei Methoden und Daten.


3. Die Lösung: Virtuelle Vererbung

Wenn beide Zwischenklassen (EBook und PrintedBook) dieselbe Basisklasse virtuell erben, existiert in der untersten Klasse (HybridBook) nur eine einzige gemeinsame Kopie der Basisklasse.


class Publication {
protected:
    string title;
public:
    Publication(string t) : title(t) { cout << "Publication()" << endl; }
    void PrintTitle() const { cout << "Titel: " << title << endl; }
};

// HINWEIS: virtuelle öffentliche Vererbung
class EBook : virtual public Publication {
public:
    EBook(string t) : Publication(t) { cout << "EBook()" << endl; }
};

class PrintedBook : virtual public Publication {
public:
    PrintedBook(string t) : Publication(t) { cout << "PrintedBook()" << endl; }
};

class HybridBook : public EBook, public PrintedBook {
public:
    HybridBook(string t)
        : Publication(t), EBook(t), PrintedBook(t) {
        cout << "HybridBook()" << endl;
    }
};

Jetzt enthält HybridBook nur noch eine Publication-Instanz – die Mehrdeutigkeit ist beseitigt:


int main() {
    HybridBook hb("Gemeinsamer Titel");
    hb.PrintTitle(); // Wird über eine einzige Publication-Instanz aufgerufen
}

Wichtig: Der am weitesten abgeleitete Typ (HybridBook) ist immer verantwortlich für die Initialisierung der virtuellen Basisklasse.


4. Konstruktionsreihenfolge und virtuelle Basen

Bei virtueller Vererbung erfolgt die Konstruktion in folgender Reihenfolge:

  1. Zuerst die virtuellen Basisklassen (eine Kopie).
  2. Dann die Zwischenklassen (EBook, PrintedBook).
  3. Zum Schluss die abgeleitete Klasse (HybridBook).

Nur die am weitesten abgeleitete Klasse initialisiert die virtuelle Basis:


class Publication {
public:
    string title;
    Publication(string t) : title(t) { cout << "Publication()" << endl; }
};

class EBook : virtual public Publication {
public:
    EBook() : Publication("dummy-ignored") { cout << "EBook()" << endl; }
};

class PrintedBook : virtual public Publication {
public:
    PrintedBook() : Publication("dummy-ignored") { cout << "PrintedBook()" << endl; }
};

class HybridBook : public EBook, public PrintedBook {
public:
    HybridBook(string t) : Publication(t), EBook(), PrintedBook() {
        cout << "HybridBook()" << endl;
    }
};

int main() {
    HybridBook hb("Gemeinsam");
    cout << hb.title << endl; // "Gemeinsam"
}

5. Mehrdeutigkeit und Lösungen

Selbst mit virtueller Vererbung können Methoden mit demselben Namen Mehrdeutigkeiten verursachen. Lösungen:


struct A { void Info() const { cout << "A" << endl; } };
struct B : virtual A { void Info() const { cout << "B" << endl; } };
struct C : virtual A { void Info() const { cout << "C" << endl; } };

struct D : B, C {
    void Info() const { cout << "D (vereinheitlicht)" << endl; }
};

6. Beispiel im Buchsystem

Ein realistischeres Beispiel: BookBase als abstrakte Basisklasse, EBook und PrintedBook erben virtuell, und HybridBook kombiniert beide Preismodelle.


#include <iostream>
#include <string>
using namespace std;

class BookBase {
protected:
    string title; string author;
public:
    BookBase(string t, string a) : title(t), author(a) {}
    virtual ~BookBase() = default;

    virtual double GetBasePrice() const = 0;
    virtual void Print() const {
        cout << title << " - " << author << endl;
    }
};

class EBook : virtual public BookBase {
protected:
    double digitalPrice; double vat;
public:
    EBook(string t, string a, double price, double v)
        : BookBase(t,a), digitalPrice(price), vat(v) {}
    double GetBasePrice() const override { return digitalPrice*(1+vat); }
};

class PrintedBook : virtual public BookBase {
protected:
    double printBase; int pages; bool hardCover;
public:
    PrintedBook(string t, string a, double base, int p, bool hard)
        : BookBase(t,a), printBase(base), pages(p), hardCover(hard) {}
    double GetBasePrice() const override {
        double cost = printBase + pages*0.03;
        if (hardCover) cost *= 1.1;
        return cost;
    }
};

class HybridBook : public EBook, public PrintedBook {
public:
    HybridBook(string t, string a,
               double digital, double vat,
               double printBase, int p, bool hard)
        : BookBase(t,a),
          EBook(t,a,digital,vat),
          PrintedBook(t,a,printBase,p,hard) {}

    double GetFinalPrice() const {
        return 0.6*EBook::GetBasePrice() + 0.4*PrintedBook::GetBasePrice();
    }

    void Print() const override {
        BookBase::Print();
        cout << "Endpreis: " << GetFinalPrice() << " €" << endl;
    }
};

int main() {
    HybridBook hb("Modern C++", "A. Dev",
                  100.0, 0.1, 50.0, 400, true);
    hb.Print();
}

7. Designhinweise und Alternativen


8. TL;DR

  • Diamond Problem: dieselbe Basisklasse wird zweimal geerbt → doppelte Kopien, Mehrdeutigkeit und Redundanz.
  • Virtuelle Vererbung sorgt für nur eine gemeinsame Basiskopie und beseitigt die Mehrdeutigkeit.
  • Der am weitesten abgeleitete Typ ruft den Konstruktor der virtuellen Basis auf.
  • Lösen Sie Mehrdeutigkeit mit Qualifizierung (A::Foo), override oder using.
  • Bevorzugen Sie Komposition oder abstrakte Schnittstellen anstelle von Mehrfachvererbung, wenn möglich.
  • Alle Beispiele sind mit Visual Studio 2022 und GCC 11+ kompatibel.

Ähnliche Artikel