====== Das Diamant-Problem ====== Mehrfachvererbung ist ein gutes Werkzeug in C++, aber wie viele gute Werkzeuge können sie auch falsch verwendet werden. Das Diamant-Problem ist ein bekanntes Problem, dass man kennen muss, wenn man professionell programmieren möchte. Die Alternative ist, Sprachen zu verwenden, die Mehrfachvererbung untersagen aufgrund der Möglichkeit Mehrfachvererbung falsch zu verwenden. Beispiele hierfür sind die populären Sprachen Java und C# ((Ich durfte soeben das Diamantproblem in C# lösen, da man Interfaces mehrfach implementieren kann. Damit schafft C# lediglich die Möglichkeiten der Mehrfachvererbung ab, während es die Probleme beibehält. siehe [[cs:diamond|Beispielprogramm]])). Damit verliert man allerdings auch die Möglichkeiten, die Mehrfachvererbung bietet. ===== Das Problem ===== Schauen wir uns das Beispiel des ''FullHDTelevision'' der [[cpp:inheritance:multiple|Mehrfachvererbung]] nocheinmal an: er ist ein ''Display'' und er ist ein ''PowerConsumer''. Definieren wir nun einen BluRay-Player: class BluRayPlayer : public PowerConsumer { private: bool DiscTrayOpen; public: BluRayPlayer() : PowerConsumer( 1, 35 ) { DiscTrayOpen = false; } }; Nachdem wir nun einen FullHDFernseher in der [[cpp:inheritance:multiple|Mehrfachvererbung]] entwickelt haben und nun auch eine Klasse BluRayPlayer haben, könnte man auf die Idee kommen, ein Produkt FullHD-Fernseher mit eingebautem BluRayPlayer zu entwickeln: class BluRayFullHDTelevision : public FullHDTelevision , public BluRayPlayer { public: BluRayFullHDTelevision() : FullHDTelevision( 3, 250 ) { } }; So schnell haben wir nicht nur die neue Klasse fertig, die Fernseher mit BluRayPlayer und genauso schnell haben wir semantischen Unsinn((Semantik beschreibt die Bedeutung einer Formulierung - im Gegensatz zur Syntax, die die richtige Verwendung bzw. Reihenfolge der Worte beschreibt)) und damit ein Diamond-Problem erzeugt. ===== Erläuterung ===== Was ist passiert? Der ''FullHDTelevision'' ist ein ''PowerConsumer'', der zwischen 3 und 250 Watt verbraucht. Und hier kommt das Diamantproblem ins Spiel: Der ''BluRayPlayer'' ist ein ''PowerConsumer'', der zwischen 1 und 35 Watt verbraucht. Die Daten für ''PowerConsumer'' tauchen also doppelt auf. Dieses Problem lässt sich nicht mehr mit [[using]] beschreiben, denn schließlich ist ein Gerät auch nur genau ein Stromverbraucher und nicht zwei.\\ {{ :cpp:inheritance:diamondproblem_small.png |Das Diamant-Problem}} ===== Lösungen ===== Es gibt zwei Möglichkeiten das Diamond-Problem zu lösen. Man kann C++ das Problem zur Laufzeit lösen lassen oder man benutzt seinen Kopf - im Idealfall bevor man etwas falsches programmiert hat. ==== Lösung 1: interne Basisklassen ===== Die effektivere Lösung sind interne Basisklassen. Hierfür setzen wir die eigentlichen Objekte möglichst spät zusammen. Das bedeutet in unserem Fall, dass wir zunächst interne Klassen erzeugen, die keinen Stromverbrauch verwalten: === Implementierung === == Der BluRayPlayer == class BluRayPlayerWithoutPowerSupply { private: bool DiscTrayOpen; public: BluRayPlayerWithoutPowerSupply() { DiscTrayOpen = false; } }; class BluRayPlayer : public BluRayPlayerWithoutPowerSupply , public PowerConsumer { public: BluRayPlayer() : PowerConsumer( 1, 35 ) { } }; == Der FullHDTelevision == class FullHDTelevisionWithoutPowerSupply : public Display { public: FullHDTelevisionWithoutPowerSupply() : Display( 1920, 1080 ) {} }; class FullHDTelevision : public FullHDTelevisionWithoutPowerSupply , public PowerConsumer { public: FullHDTelevision( int minWatts, int maxWatts ) : PowerConsumer( minWatts, maxWatts ) {} }; == Das Kombi-Gerät == class BluRayFullHDTelevision : public FullHDTelevisionWithoutPowerSupply , public BluRayPlayerWithoutPowerSupply , public PowerConsumer { public: BluRayFullHDTelevision() : PowerConsumer( 4, 285 ) { } }; === Fazit === ''FullHDTelevision'' und ''BluRayPlayer'' funktionieren identisch zu den Varianten, wie sie vorher waren. Die beiden Zwischenklassen ''FullHDTelevisionWithoutPowerSupply'' und ''BluRayPlayerWithoutPowerSupply'' sind für den Entwickler außerhalb dieser Implementierung uninteressant. Sie erlauben aber die Klasse ''BluRayFullHDTelevision'' zusammenzubauen, ohne das Diamond-Problem hervorzurufen. Alle Geräte sind genau einmal von ''PowerConsumer'' abgeleitet und können an Funktionen übergeben werden, die einen ''PowerConsumer'' als Parameter wünschen. Die FullHD-Bildschirme können über ''FullHDTelevisionWithoutPowerSupply'' übergeben werden. Methoden, die ein ''FullHDTelevisionWithoutPowerSupply'' können also mit einem ''FullHDTelevision'' und einem ''BluRayFullHDTelevision'' gefüttert werden. Nun haben wir auch das richtige Gerät beschrieben: Statt eines Displays mit Netzteil und einem BluRay-Laufwerk mit Netzteil haben wir ein Gerät beschrieben, dass aus einem Display, einem BluRay-Laufwerk und genau einem Netzteil besteht. ==== Lösung 2: virtuelle Ableitungen ===== Im Fazit der ersten Lösung wurde gezeigt, dass mit einer überlegten Anordnung der Ableitungshierarchie das Diamantproblem gelöst wurde und soweit alle wichtigen Möglichkeiten mit einer passenden Basisklasse als Parameter gegeben sind. Alle? Wir haben zwei verschiedene Bildschirmtypen und wir können mit ''FullHDTelevisionWithoutPowerSupply'' die beiden dazugehörigen Typen ''BluRayFullHDTelevision'' und ''FullHDTelevision'' zusammenfassen. Aber ein ''FullHDTelevisionWithoutPowerSupply'' erlaubt - wie der Name schon sagt - keinen Zugriff auf die Klasse ''PowerConsumer'', obwohl sowohl ''BluRayFullHDTelevision'' als auch ''FullHDTelevision'' jeweils ein ''PowerConsumer'' sind. Man könnte dieses Beispiel nun so ändern, dass ein ''FullHDTelevision'' von ''PowerConsumer'' abgeleitet wird und ''BluRayFullHDTelevision'' von ''BluRayPlayerWithoutPowerSupply'' und den ''PowerConsumer'' von ''FullHDTelevision'' im Konstruktor auf die richtigen Werte korrigiert. Dann hätten wir aber das gleiche Problem mit dem ''BluRayPlayer'', bei dem es dann keine Oberklasse gäbe, der einen ''BluRayPlayer'' inkl. ''PowerConsumer'' abfragt. Dies ist mit [[virtual|virtuellen Ableitungen]] möglich.