.TL Virtuelle Mehrfachvererbung .IP Das gegebene Programm hat folgende virtuelle Mehrfachvererbung: .PS ellipseht = 1 ellipsewid = 2 A: ellipse "A" AB: arrow down left from A.sw B: ellipse "B" with .ne at AB.b BD: arrow down right from B.se AC: arrow down right from A.se C: ellipse "C" with .nw at AC.b CD: arrow down left from C.sw D: ellipse "D" with .ne at CD.b .PE .DS #include class A { public: virtual void f(); }; class B: virtual public A { public: virtual void f(); }; class C: virtual public A { }; class D: public B, public C { }; main() { D d; A *ap = &d; ap->f(); } void A::f() { cout << "A::f" << endl; } void B::f() { cout << "B::f" << endl; } .DE .bp .LG Ohne das Schlüsselwort virtual bei der Ableitung von B und C wäre &D gar nicht nach A* castbar, da dann D::B::A und D::C::A isoliert voneinander existierten. Der Zusatz virtual sorgt dafür, daß virtuelle Member nur genau eine Bedeutung erhalten und ein Mehrwegvererbungskonflikt von Komponenten erkannt und gelöst wird. Das Problem ist hier folgendes: D könnte sowohl von B als auch von C eine virtuelle Funktion für f erben, denn A::f wurde nach C als default virtual übertragen. Man könnte auch erwarten, daß A::f() alleine deswegen aufgerufen wird, da ap ja ein Pointer nach Typ A ist, und die Zuweisung ap = & d als impliziter Cast mißverstanden werden könnte. Beide Compiler sorgen jedoch dafür, daß als Ergebnis, wie von Bjarne Stroustrup gefordert, B::f ausgegeben wird. Da B f später als A definiert, überschreibt es nämlich dessen Bedeutung für sich und D. A ist nicht explizit als Basisklasse für D angegeben. Wäre sie es, würde CC sich weigern, die entstehende Mehrdeutigkeit zu akzeptieren. Es ist offensichtlich nicht entscheidend, welcher Klasse der Zeiger angehört, durch den f() aufgerufen wird, sondern welcher Klasse das referenzierte Objekt angehört. Jedes Objekt muß also eine Tabelle der ihm zugeordneten virtuellen Funktionen mit sich führen. Dieses Prinzip macht Sinn, denn allgemeine Pointer für Objekte aus A \(cu B \(cu C \(cu D müssen ja den abstraktesten Basistyp A haben und sollten trotzdem auf die passenden Methoden der konkreten Objekte wie beispielsweise B zugreifen können. Um die zugrundeliegende Technik genau kennenzulernen, habe ich mir mal die Mühe gemacht, den generierten C-Code zu indentieren, welcher trotz des relativ einfachen Programms recht unübersichtlich ist. Hier findet sich erst ein Haufen Definitionen für iostreams und dann die Strukturen A, B, C, D. Wie erwartet, enthalten sie Komponenten namens ,,mptr``, wohinter sich wahrscheinlich Methodenpointer, also Tabellen virtueller Funktionen, verbergen. Die Funktion DFv ist mit Abstand die komplizierteste. Mehr kann ich dazu nicht sagen, da ich mich im Compilerbau noch nicht auskenne, und mir dieser kryptische Programmtext zu unverständlich ist.