DerAlbi hat geschrieben:Doppelpost, weil ich vorhin Dot noch nicht gesehen hatte, bevor ich im Antwort-Formular war.
Was genau ist das Problem daran, dass BasicBlab "kompatibel" zu IBlaBlub ist. Wieso haben IBaseInterface und IOtherBaseInterface in deinem Beispiel dann zwei verschiedene Methoden mit dem selben Namen, wieso ist eine davon pure Virtual!?
Weil ich sicherstellen muss, dass die Methode im Kind implementiert ist. Also sie muss dort als implementiert erscheinen. Implementiert worden sein kann sie natürlich von jemanden parallel in der Hierarchie.
Ich nehme an, dass dir mittlerweile eh klar ist, dass das genau das ist, was die vorgeschlagene Lösung, Interfaces über virtuelle Basisklassen zu repräsentieren, tut... ;)
DerAlbi hat geschrieben:Das mit der virtuellen Vererbung muss ich jetzt aber erstmal noch nachlesen. So 100% klar ist mir nicht, warum das nun alles funktioniert ^_^
Kurzfassung:
In deinem ursprünglichen Ansatz hattest du zwei verschiedene Klassen, die beide eine Methode mit dem selben Namen hatten. Eine "normale" Basisklasse wird einfach zu einem Subobjekt der abgeleiteten Klasse und fertig. Für dein
CDerived bedeutete das, dass es ein
IBaseInterface Subobjekt und ein
IOtherBaseInterface Subobjekt hatte, beide völlig unabhängige Typen die zufällig jeweils eine Methode namens
Implement haben. Dein
CDerived erbt beide
Implement Methoden, du kannst sie über
CDerived aber nie aufrufen da dein
CDerived eben zwei verschiedene Methoden – eine für das erste Subobjekt und eine für das zweite – mit gleichem Namen hat und der Name-Lookup daher mehrdeutig ist. Was du machen kannst ist dein
CDerived auf die jeweilige Basisklasse zu casten und so explizit erst das Subobjekt auszuwählen und dann direkt die entsprechende
Implement Methode darauf aufzurufen, einen qualifizierten Methodennamen anzugeben (z.B.
d.IBaseInterface::Implement()), oder per using-Deklaration explizit einen der beiden Names in
CDerived einzufüren. Sobald dein
CDerived selbst eine Methode namens
Implement deklariert, shadowed dieser Name die gleichnamigen Methoden der beiden Basisklassen und der Name-Lookup ist wieder eindeutig. Alles was du damit erreichst ist aber, dass eindeutig bestimmt ist, welche Methode von welchem Subobjekt nun aufgerufen wird. Keine dieser Vorgehensweisen löst das eigentliche Problem, welches da ist, dass es sich bei deinen Klassen und Methoden um völlig unabhängige Klassen und Methoden handelt, die einfach nur nebeneinander in ein Objekt gesteckt wurden ohne dabei irgendeinen Zusammenhang zu modellieren.
Was du eigentlich willst, ist, dass es ein Interface mit Methoden und Base-Classes mit Standardimplementierungen einiger dieser Methoden gibt. Eine konkrete Klasse soll das Interface implementieren und gleichzeitig Standardimplementierungen hereinerben, teilweise weiter überschreiben sowie noch nicht implementierte Interfacemethoden definieren können. Damit das geht müssen deine Standardimplementierungsklassen aber die jeweiligen Methoden des ursprünglichen Interface überschreiben und nicht einfach nur zusammenhangslos neue Methoden mit dem selben Namen hinzufügen. Damit eine Standardimplementierungsklasse Methoden des Interface überschreiben kann, muss sie selbst vom Interface ableiten. Wenn du einfach nur zwei unverwandte Basisklassen mit gleichnamigen Methoden hast, dann gibt es zwischen diesen Methoden keinerlei Beziehung. Das sind dann einfach zwei verschiedene Methoden die nichts miteinander zu tun haben; sie liegen in separaten, nicht verschachtelten Namensräumen, so etwas wie das Konzept von Überschreibung macht zwischen solchen Strukturen nichtmal Sinn.
Dabei kommt es nun zu folgendem Problem: Deine konkrete Klasse soll das Interface implementieren, muss also vom Interface erben. Da sie vom Interface erbt, hat sie ein Subobjekt vom entsprechenden Typ. Deine Standardimplementierungsklassen müssen vom Interface erben. Da sie vom Interface erben, haben sie jeweils ein eigenes Subobjekt vom entsprechenden Typ. Deine konkrete Klasse soll zusätzlich zum Interface auch von einigen Standardimplementierungsklassen erben, sie hat daher zusätzlich ein Subobjekt für jede Standardimplementierung. Und jedes dieser Subobjekte hat nochmals ein eigenes Interface-Subobjekt. Das allein hilft uns also leider noch nicht; unsere Namensräume haben nun zwar eine passende Hierarchie, aber da unsere konkrete Klasse sowie jede Standardimplementierungsklasse ihr eigenes Interface-Subobjekt mitbringt haben wir immer noch separate Überschreibungsketten der Interface Methoden für jedes Interface-Subobjekt (anders ausgedrückt: Für jedes Interface-Subobjekt existiert ein separater Satz der Interface Methoden). Was wir eigentlich wollen ist, dass sowohl unsere konkrete Klasse als auch alle Standardimplementierungsklassen sich ein Interface-Subobjekt teilen. Dann gibt es für jede Methode dieses gemeinsamen Interface-Subobjektes auch nur eine gemeinsame Überschreibungskette. Und genau das macht die virtuelle Vererbung. Eine virtuelle Basisklasse wird nicht einfach direkt zu einem Subobjekt der abgeleiteten Klasse. Die abgeleitete Klasse enthält stattdessen einen Pointer auf das zu verwendende Basisklassenobjekt. Für jede virtuelle Basisklasse in einer Vererbungshierarchie wird nur ein Subobjekt im most-derived Object angelegt das von allen geteilt wird, indem deren virtual Base-Pointer auf die Adresse des gemeinsamen Basisklassenobjektes gesetzt werden.