2010-06-23 9 views
7

Bitte beachten Sie den folgenden Code eines Überladene:virtueller Funktionsaufruf Auflösung

class Abase{}; 
class A1:public Abase{}; 
class A2:public A1{}; 
//etc 

class Bbase{ 
public: 
    virtual void f(Abase* a); 
    virtual void f(A1* a); 
    virtual void f(A2* a); 
}; 

class B1:public Bbase{ 
public: 
    void f(A1* a); 
}; 

class B2:public Bbase{ 
public: 
    void f(A2* a); 
}; 

int main(){ 
    A1* a1=new A1(); 
    A2* a2=new A2(); 
    Bbase* b1=new B1(); 
    Bbase* b2=new B2(); 
    b1->f(a1); // calls B1::f(A1*), ok 
    b2->f(a2); // calls B2::f(A2*), ok 
    b2->f(a1); // calls Bbase::f(A1*), ok 
    b1->f(a2); // calls Bbase::f(A2*), no- want B1::f(A1*)! 
} 

Ich bin daran interessiert zu wissen, warum C++ den Funktionsaufruf in der letzten Zeile zu lösen wählt durch Upcasting die this Zeiger des Objekts an die Basis Klasse, anstatt das Argument der f() upcasting? Gibt es irgendeine Möglichkeit, dass ich das gewünschte Verhalten bekomme?

Antwort

10

Die Auswahl, welche Version von f aufgerufen werden soll, erfolgt durch einen Blick auf den Kompilierzeit Typ des Parameters. Der Laufzeittyp wird für diese Namensauflösung nicht berücksichtigt. Da b1 vom Typ Bbase* ist, werden alle Mitglieder der Bbase berücksichtigt; derjenige, der eine A2* nimmt, ist die beste Übereinstimmung, also ist derjenige, der aufgerufen wird.

+0

Danke - wie ich es verstehe, ist der Punkt, dass die Auflösung, die virtuell f() aufgerufen wird, zur Kompilierzeit geschieht, basierend auf dem Argument, das an f() übergeben wird. In der letzten Zeile hat der Compiler bereits entschieden, dass f (A2 *) aufgerufen wird. Die aufgerufene Version von f (A2 *) hängt dann davon ab, auf welchen Laufzeittyp verwiesen wird. Da die Klasse B1 f (A2 *) nicht übersteuert, wird hier die Basisklassenversion aufgerufen. – stw

+0

@stw, absolut richtig. –

1
b1->f(static_cast<A1*>(a2)); 

Dies sollte den Compiler zwingen, die Überladungsmethode mit Parameter vom Typ A1 zu verwenden.

+0

ein dynamic_cast besser geeignet ist, glaube ich. – Jason

+2

@Jason: Ein 'static_cast' zu einer Basisklasse ist in Ordnung. –

+2

Ich denke, eine statische Besetzung ist in Ordnung, da wir a) wissen, dass die Besetzung korrekt ist und b) es eine Upcast ist, die sicher ist. –

0

Es heißt Name Verstecken. Jedes f, das Sie in einer abgeleiteten Klasse deklarieren, schattiert jedes mögliche f in irgendeiner seiner Basisklassen.

Verwenden Sie eine Umwandlung in die Basisklasse, um das gewünschte Verhalten zu erzielen.

Wenn Sie eine virtuelle Funktion überschreiben, überschreiben Sie keine überladenen Funktionen mit demselben Namen. Sie sind verschiedene Funktionen (und haben unterschiedliche Einträge in der vtable).

+0

Nein, das Problem hier ist umgekehrt. Wenn Sie über einen Zeiger auf eine abgeleitete Klasse aufrufen würden, die die Methode (n) aus der Basisklasse ausblendet, würde dies den Namen verbergen. –

+0

Hoppla, du hast recht, ich habe zu schnell gelesen. –

2

"... wählt den Funktionsaufruf in der letzten Zeile auf, indem der Zeiger des Objekts auf die Basisklasse umgestellt wird ...". Worüber redest du? In allen Ihren Aufrufen ist der Objekt-Zeigertyp Bbase * und die Funktionen, deren Auflösungen zu oder seinen Nachkommen gehören. Der Compiler führt keine Upcasts durch, um Ihre Anrufe aufzulösen. Tatsächlich erfordern die ersten beiden Aufrufe downcasting, um den richtigen Overrider aufzurufen, da der Overrider zu der Klasse gehört, die sich weiter unten in der Hierarchie befindet. Wie für die letzten zwei Anrufe - sie werden in die Bbase Klasse durch einen Zeiger von Bbase * Typ geschickt. Die Typen stimmen genau überein, es findet kein Guss statt.

Zur Überladungsauflösung ... Die Überladungsauflösung ist ein Kompilierzeitprozess, der auf den statischen Typen der Argumente und den Rängen möglicher Konvertierungen basiert. Sie haben ein Argument vom Typ A2 * angegeben. Der f(A2 *) Kandidat entspricht Ihrem Argument genau. Der f(A1 *) Kandidat erfordert eine zusätzliche Umwandlung von A2 * zu A1 *. Der Kandidat, der genau übereinstimmt, wird als besser erachtet, also gewinnt er die Überladungsauflösung. Einfach.

+0

Entschuldigung, ich habe den Begriff "Upcasting" falsch verwendet. Was ich meinte, war, warum das Objekt hier als BBase behandelt wird. Die Antwort (wie Sie sagen) ist, dass die Überladungsauflösung zur Kompilierungszeit auftritt, und zur Kompilierungszeit ist das Objekt eine BBase, so dass der Compiler f (A2 *) wählt. – stw

0

Ihre Überladungen in Bbase für Abase und A2 sind in B1 ausgeblendet. Vielleicht können Sie das Problem wie folgt funktionieren:

class Bbase{ 
public: 
    inline void f(Abase* a) { f_(a); } 
    inline void f(A1* a) { f_(a); } 
    inline void f(A2* a) { f_(a); } 
protected: 
    virtual void f_(Abase* a); 
    virtual void f_(A1* a); 
    virtual void f_(A2* a); 
}; 

class B1:public Bbase{ 
protected: 
    void f_(A1* a); 
}; 

class B2:public Bbase{ 
protected: 
    void f_(A2* a); 
}; 

oder mit einer Vorlage in Bbase:

class Bbase{ 
public: 
    template<class myA> 
    inline void f(myA* a) { f_(a); } 
protected: 
    virtual void f_(Abase* a); 
    virtual void f_(A1* a); 
    virtual void f_(A2* a); 
};