In C++ kann eine Klasse von mehreren Klassen erben. Dieses Feature ist sehr umstritten und in einigen anderen Sprachen (Java, C#,... ) ist deshalb mehrfach Vererbung verboten. Aber auch dort kann eine Klasse von mehreren Interfaces erben. Ein Interface ist eine Art abstrakte Klasse.
Bei der mehrfach Vererbung kann es zu folgendem Problem kommen (Deadly Diamond Of Death)
class UltimateBase
{
};
class Base1 : public UltimateBase
{
};
class Base2 : public UltimateBase
{
};
class Derived : public Base1, public Base2
{
};
Derived hat 2mal UltimateBase als Basisklasse. Folglich müsste UltimateBase 2mal konstruiert werden. Das wollen wir aber meistens nicht. Es führt ja auch zu Uneindeutigkeiten:
#include<iostream>
using namespace std;
class UltimateBase
{
private:
int value;
public:
UltimateBase(int value)
: value(value)
{}
int print() const
{
return value;
}
};
class Base1 : public UltimateBase
{
public:
Base1(int value) : UltimateBase(value)
{}
};
class Base2 : public UltimateBase
{
public:
Base2(int value) : UltimateBase(value)
{}
};
class Derived : public Base1, public Base2
{
public:
Derived(int a, int b)
: Base1(a), Base2(b)
{}
};
int main()
{
Derived d(3,4);
cout<<d.print()<<'\n';
}
Welcher Wert wird ausgegeben? Niemand weiß es, auch der Compiler nicht. Er wird uns deshalb sagen, dass d.print(); Uneindeutig (ambiguous) ist. Doch wie können wir dieses Problem umgehen? Es gibt 2 mögliche Lösungen. Wir können explizit sagen, welches print wir wollen:
#include<iostream>
using namespace std;
class UltimateBase
{
private:
int value;
public:
UltimateBase(int value)
: value(value)
{}
int print() const
{
return value;
}
};
class Base1 : public UltimateBase
{
public:
Base1(int value) : UltimateBase(value)
{}
};
class Base2 : public UltimateBase
{
public:
Base2(int value) : UltimateBase(value)
{}
};
class Derived : public Base1, public Base2
{
public:
Derived(int a, int b)
: Base1(a), Base2(b)
{}
};
int main()
{
Derived d(3,4);
cout<<d.Base1::print()<<'\n';
cout<<d.Base2::print()<<'\n';
}
Doch des öfteren wollen wir diese Entscheidung nicht treffen, weil wir UltimateBase nur einmal haben wollen. Hier brauchen wir virtual Vererbung:
#include<iostream>
using namespace std;
class UltimateBase
{
private:
int value;
public:
UltimateBase(int value)
: value(value)
{}
int print() const
{
return value;
}
};
class Base1 : virtual public UltimateBase
{
public:
Base1(int value) : UltimateBase(value)
{}
};
class Base2 : virtual public UltimateBase
{
public:
Base2(int value) : UltimateBase(value)
{}
};
class Derived : public Base1, public Base2
{
public:
Derived(int a, int b)
: Base1(a), Base2(b)
{}
};
int main()
{
Derived d(3,4);
cout<<d.print()<<'\n';
}
Jetzt ist es eindeutig welches print() gemeint ist, es gibt ja nur eins. Doch wird jetzt 3 oder 4 ausgegeben? Die Antwort ist 3. Denn eine virtuelle Basisklasse wird nur 1mal Initialisiert (logisch, sonst gäbe es sie ja mehr als nur 1 mal) und wie im echten Leben gilt auch hier: wer zuerst kommt, mahlt zuerst. Da Base1 zuerst UltimateBase intialisiert, wird UltimateBase mit dem Wert 3 initialisiert.
Oft (aber nicht immer) ist die Verwendung einer virtuellen Basisklasse ein Zeichen von Designproblem. Siehe auch GotW 37: Multiple Inheritance - Part I.