====== Virtuelle Ableitungen ====== ===== Das Problem ===== Wir hatten uns zuvor das [[diamond|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 ) { } }; {{ :cpp:inheritance:bdfullhdtelevision.png |}} und stehen nun erneut vor der Frage, wie wir das [[diamond|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. ===== Die Lösung ===== 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. {{:cpp:inheritance:vblurayplayer.png|}} {{:cpp:inheritance:vfullhdtelevision.png|}} 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. Nun sieht unsere Klasse so aus: {{ :cpp:inheritance:vblurayfullhdtelevision.png |}} ===== Bedeutung von virtuellen Ableitungen ===== 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: {{ :cpp:inheritance:virtualinheritance_small.png |}}