4

Betrachten Sie den folgenden Code ein:eine Funktion von Schwester Klasse C++ Aufruf

#include <iostream> 

class A 
{ 
public: 
    virtual void f() = 0; 
    virtual void g() = 0; 
}; 

class B : virtual public A 
{ 
public: 
    virtual void f() 
    { 
     g(); 
    } 
}; 

class C : virtual public A 
{ 
public: 
    virtual void g() 
    { 
     std::cout << "C::g" << std::endl; 
    } 
}; 

class D : public C, public B 
{ 
}; 

int main() 
{ 
    B* b = new D; 
    b->f(); 
} 

Der Ausgang des folgenden Programms ist C::g.

Wie ruft der Compiler eine Funktion einer Schwesterklasse der Klasse B auf?

+0

Tu dies nicht, das ist der sogenannte Diamant des Todes, siehe https://en.wikipedia.org/wiki/Multiple_inheritance – Bernhard

+0

Ich weiß was es ist, ich will wissen wie es funktioniert. –

+4

Es könnte helfen, wenn Sie erklären, warum Sie denken, dass es so nicht funktionieren würde. Wären Sie auch verwirrt, wenn Sie die erste Zeile von 'main' in' A * b = new D; '? – Hurkyl

Antwort

6

N3337 10,3/9

[Anmerkung: Die Interpretation des Aufrufs einer virtuellen Funktion hängt von der Art des Objekts, für die sie aufgerufen (den dynamischen Typen), während Die Interpretation eines Aufrufs einer nicht virtuellen Elementfunktion hängt nur vom Typ des Zeigers oder Verweises ab, der dieses Objekt bezeichnet (der statische Typ) (5.2.2).- Endnote]

Der dynamische Typ ist der Typ, auf die Zeiger wirklich Punkte, geben Sie nicht das war als spitzer Typ deklariert.

Deshalb:

D d; 
d.g(); //this results in C::g as expected 

ist die gleiche wie:

B* b = new D; 
b->g(); 

Und weil in Ihrem B::f Aufruf g() ist (implizit) genannt auf this Pointer, dessen dynamischen Typ ist D, Anruf löst sich auf D::f, die C::f ist.

Wenn Sie genau hinsehen, es ist das (genau) gleiche Verhalten wie oben im Code gezeigt, nur dass b jetzt this stattdessen implizit ist.

Das ist der springende Punkt der virtuellen Funktionen.

2

Es ist das Verhalten von virtual: B Aufruf g durch f aber g wird zur Laufzeit lösen (wie f). Also, zur Laufzeit, ist die einzige verfügbare Überschreibung von g für D derjenige implementiert in C

1

g wird zur Laufzeit aufgelöst, wie alle virtuellen Funktionen. Wegen der Art, wie definiert ist, ist es in was auch immer C implementiert aufgelöst.

Wenn Sie dieses Verhalten nicht möchten, dass Sie entweder eine nicht-virtuelle Implementierung von g nennen soll (Sie können auf diese Funktion aus dem virtuellen delegieren als auch), oder rufen Sie ausdrücklich B ‚s Implementierung B::g() verwenden.

Obwohl, wenn Sie dies tun, Ihr Design wird viel komplizierter als es wahrscheinlich sein muss, also versuchen Sie, eine Lösung zu finden, die nicht auf all diesen Tricks beruht.

0

Virtuelle Funktionsaufrufe werden zur Laufzeit mit Bezug auf VTable der Instanz aufgelöst. Eine eindeutige VTable existiert für jede virtuelle Klasse (so in oben jeder von A, B, C und haben eine VTable). Jede Laufzeitinstanz hat einen Zeiger auf eine dieser Tabellen, die durch ihren dynamischen Typ bestimmt wird.

Die VTable listet jede virtuelle Funktion in der Klasse auf und ordnet sie der tatsächlichen Funktion zu, die zur Laufzeit aufgerufen werden soll. Für die normale Vererbung werden diese in der Reihenfolge der Deklaration aufgelistet, sodass eine Basisklasse das VTable einer abgeleiteten Klasse zum Deklarieren virtueller Funktionen verwenden kann, die deklariert werden (weil die abgeleitete Klasse, die die Funktionen in Deklarationsreihenfolge aufführt, alle Funktionen der Basisklasse hat) zuerst in genau der gleichen Reihenfolge der Basisklasse eigene VTable aufgeführt). Für die virtuelle Vererbung (wie oben) ist dies etwas komplizierter, aber im Wesentlichen hat die Basisklasse innerhalb der abgeleiteten Klasse immer noch ihren eigenen Zeiger VTable, der auf den relevanten Abschnitt innerhalb der abgeleiteten VTable zeigt.

In der Praxis bedeutet dies, Ihre D Klasse einen VTable Eintrag für g hat, die C ‚s Implementierung zeigt. Selbst wenn über den statischen Typ B zugegriffen wird, wird immer noch auf VTable verwiesen, um g aufzulösen.

+0

"_Eine eindeutige VTabelle existiert für jede deklarierte Klasse_" 1) nur für "virtuelle" Klassen, nicht alle deklarierten Klassen; 2) mindestens eine vtable, nicht genau eine – curiousguy