Wir hatten uns zuvor das Diamant-Problem angesehen und festgestellt, dass eine durchdachte Klassenhierarchie viele Probleme lösen kann. Nun wollen wir uns ansehen, wie die Datentypen BluRayPlayer
, FullHDTelevision
und BluRayFullHDTelevision
so miteinander verknüpft, dass die Oberklassen von BluRayFullHDTelevision bereits vollständige Geräte sind - also von PowerConsumer
abgeleitet sind.
Wir graben also wieder die ursprüngliche Deklaration von BluRayFullHDTelevision
aus
class BluRayFullHDTelevision : public FullHDTelevision , public BluRayPlayer { public: BluRayFullHDTelevision() : FullHDTelevision( 3, 250 ) { } };
und stehen nun erneut vor der Frage, wie wir das Diamant-Problem lösen wollen:
FullHdTelevision
und BluRayPlayer
haben jeweils ihr eigenes Netzteil eingebaut, denn sie sind beide von PowerConsumer
abgeleitet. Weiterhin muss die Klassenhierarchie so aufgebaut sein, dass man eine Funktion schreiben kann, die als Parameter einen BluRayPlayer &
bekommt und beim BluRayPlayer
, wie auch beim BluRayFullHDTelevision
so Zugriff auf das einzige vorhandene Netzteil erhält und man weiterin eine Funktion schreiben kann, die als Parameter einen FullHDTelevision &
erhält und beim FullHDTelevision
, wie auch beim BluRayFullHDTelevision
genauso Zugriff auf das einzige vorhandene Netzteil erhält. Im Fall des BluRayFullHDTelevision
muss es sich also um das gleiche Netzteil (also um die gleiche Instanz der Klasse PowerConsumer
) handeln - unabhängig davon, ob man das BluRayFullHDTelevision
als BluRayPlayer
oder als FullHDTelevision
referenziert.
Hier kommen sogenannte virtuelle Ableitungen ins Spiel.
Bisher sehen unsere Oberklassen wie folgt aus:
class BluRayPlayer : public PowerConsumer { private: bool DiscTrayOpen; public: BluRayPlayer() : PowerConsumer( 1, 35 ) { DiscTrayOpen = false; } }; class FullHDTelevision : public Display , public PowerConsumer { public: FullHDTelevision( int minWatts, int maxWatts ) : Display( 1920, 1080 ) , PowerConsumer( minWatts, maxWatts ) {} };
Die Aussage 'ist ein PowerConsumer
' trifft auf beide Geräte zu. Die direkte Ableitung lötet das Netzteil gewissermaßen fest in das Gerät ein. Die virtuelle Ableitung löst nun das Netzteil aus dem Gerät und sagt gewissermaßen folgendes aus: 'ein PowerConsumer
wird mitgeliefert'. Stellen wir uns einen BluRayPlayer mit externen Netzteil vor: das Gerät funktioniert nur, wenn das Netzteil mitgeliefert wird. Eine Instanz von BluRayPlayer wird also immer mit Netzteil erzeugt und wenn die Klasse verwendet wird, ist garantiert, dass ein Netzteil vorhanden ist und der BluRayPlayer Strom konsumiert. Dennoch ist das Netzteil nicht fest mit dem Player verlötet, sondern kann ausgetauscht werden.
So lassen sich auch Basisklassen aus den Objekten lösen:
class BluRayPlayer : virtual public PowerConsumer // <- virtuelle Ableitung { private: bool DiscTrayOpen; public: BluRayPlayer() : PowerConsumer( 1, 35 ) { DiscTrayOpen = false; } }; class FullHDTelevision : public Display , virtual public PowerConsumer // <- virtuelle Ableitung { public: FullHDTelevision( int minWatts, int maxWatts ) : Display( 1920, 1080 ) , PowerConsumer( minWatts, maxWatts ) {} };
Anschließend wird der BluRayPlayer unverändert wie zuvor deklariert:
class BluRayFullHDTelevision : public FullHDTelevision , public BluRayPlayer { public: BluRayFullHDTelevision() : FullHDTelevision( 3, 250 ) , PowerConsumer( 3, 250 ) { } };
Die beiden Klassen FullHDTelevision
und BluRayPlayer
verlangen, dass ein Netzteil mitgeliefert wird. Durch die virtuelle Ableitung ist aber ausgesagt, dass sie sich ein Netzteil mit einem anderen Gerät teilen können. Wäre das nicht möglich, müsste jeder seinen eigenen PowerConsumer
haben, was einer Standard-Ableitung entspricht.
Unser FullHD-Fernseher-Konstructor verlangt nun die Information, wieviel Leistung er aufnimmt. Er wird aber den virtuellen Konstruktor damit nicht mehr aufrufen - man könnte einen Default-Konstruktor anlegen, der öffentlich nicht sichtbar ist, sondern nur von abgeleiteten (protected
) Klassen gerufen werden kann - wie beispielsweise der Klasse BluRayFullHDTelevision
. Der virtuelle Konstruktor muss immer auch von der Klasse aufgerufen werden, in der sich mehrere virtuelle Ableitungen treffen. BluRayFullHDTelevision
muss also PowerConsumer eindeutig bestücken.
Bei einer normalen Ableitung erzeugt eine Instanz der Ableitung einen Speicherbereich in dem die Daten der Basisklasse und die Erweiterungen der Ableitung gespeichert werden können. Die Basisklasse und die Ableitung liegen also direkt hintereinander im Speicher.
Bei der Mehrfachvererbung liegen so mehrere Klassen hintereinander im Speicher. Da BluRayPlayer
und FullHDTelevision
beide virtuell von PowerConsumer
abgeleitet sind, liegen wird für beide Klassen nur ein PowerConsumer
erzeugt und dieser in die Klasse BluRayFullHDTelevision
gelegt. Die beiden Klassen BluRayPlayer
und FullHDTelevision
greifen nun über ihren Virtual-Zeiger auf die PowerConsumer
-Instanz von BluRayFullHDTelevision
zu und haben selbst keinen eigenen mehr: