2009-06-09 11 views
21

Während ich mit der Implementierung eines virtuellen Zuweisungsoperators spielte, endete ich mit einem lustigen Verhalten. Es ist kein Compilerfehler, da g ++ 4.1, 4.3 und VS 2005 das gleiche Verhalten haben.Warum verhält sich die virtuelle Zuweisung anders als andere virtuelle Funktionen derselben Signatur?

Grundsätzlich verhält sich der virtuelle Operator = anders als jede andere virtuelle Funktion in Bezug auf den Code, der tatsächlich ausgeführt wird.

struct Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Base::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Base::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
struct Derived : public Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Derived::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Derived::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
int main() { 
    Derived a, b; 

    a.f(b); // [0] outputs: Derived::f(Base const &) (expected result) 
    a = b; // [1] outputs: Base::operator=(Base const &) 

    Base & ba = a; 
    Base & bb = b; 
    ba = bb; // [2] outputs: Derived::operator=(Base const &) 

    Derived & da = a; 
    Derived & db = b; 
    da = db; // [3] outputs: Base::operator=(Base const &) 

    ba = da; // [4] outputs: Derived::operator=(Base const &) 
    da = ba; // [5] outputs: Derived::operator=(Base const &) 
} 

Der Effekt ist, dass der virtuelle Operator = ein anderes Verhalten als jede andere virtuelle Funktion mit der gleichen Signatur hat ([0] im Vergleich zu [1]), die durch die Basisversion des Betreibers aufrufen, wenn sie aufgerufen durch reale abgeleitete Objekte ([1]) oder abgeleitete Referenzen ([3]), während sie als reguläre virtuelle Funktion ausgeführt werden, wenn sie durch Basisreferenzen ([2]) aufgerufen werden, oder wenn der lvalue oder rvalue Basisreferenzen und die anderen a sind Abgeleitete Referenz ([4], [5]).

Gibt es eine vernünftige Erklärung für dieses seltsame Verhalten?

Antwort

13

hier, wie es geht:

Wenn ich ändern [1] bis

a = *((Base*)&b); 

dann die Dinge so funktionieren, wie Sie es erwarten. Es gibt eine automatisch generierte Zuweisungsoperator in Derived die wie folgt aussieht:

Derived& operator=(Derived const & that) { 
    Base::operator=(that); 
    // rewrite all Derived members by using their assignment operator, for example 
    foo = that.foo; 
    bar = that.bar; 
    return *this; 
} 

In Ihrem Beispiel Compiler haben genug Informationen zu erraten, dass a und b vom Typ sind Derived und so wählen sie den automatisch generierten Operator verwenden darüber ruft deine. So hast du [1]. Mein Pointercasting zwingt Compiler dazu, es auf Ihre Art zu tun, weil ich dem Compiler sage "vergessen", dass b vom Typ Derived ist und so Base verwendet.

Andere Ergebnisse können auf die gleiche Weise erklärt werden.

+3

keine Es gibt hier beteiligt zu erraten. Die Regeln sind sehr streng. – MSalters

+0

Danke, Die wirkliche Antwort (wie von bereits drei Leuten geschrieben) ist, dass der Compiler generated = Operator für die abgeleitete Klasse implizit den Base :: operator = aufruft. Ich markiere dies als "akzeptierte Antwort", da es die erste war. –

+0

'a = static_cast (b);' wäre eine Möglichkeit, C-Style-Casts zu vermeiden (die das Risiko einer versehentlichen Neuinterpretierung beinhalten). –

4

Es gibt keinen vom Benutzer bereitgestellten Zuweisungsoperator, der für die abgeleitete Klasse definiert ist. Daher synthetisiert der Compiler einen und der interne Basisklassenzuweisungsoperator wird von diesem synthetisierten Zuweisungsoperator für die abgeleitete Klasse aufgerufen. Daher

virtual Base& operator=(Base const &) //is not assignment operator for Derived 

, a = b; // [1] outputs: Base::operator=(Base const &)

In abgeleiteten Klasse hat der Basisklassenzuweisungsoperator wurde außer Kraft gesetzt, und daher wird die überschriebene Methode einen Eintrag in der virtuellen Tabelle der abgeleiteten Klasse. Wenn die Methode über Referenz oder Zeiger aufgerufen wird, wird die Methode Overrided der abgeleiteten Klasse wegen der Auflösung des VTable-Eintrags zur Laufzeit aufgerufen.

ba = bb; // [2] outputs: Derived::operator=(Base const &) 

==> intern ==> (Object-> VTable [assignement operator]) Holen den Eintrag für Zuweisungsoperator in VTable der Klasse, zu der das Objekt gehört, und die Methode aufzurufen.

3

Wenn Sie einen entsprechenden operator= (d. H. Korrekte Rückgabe- und Argumenttypen) nicht bereitstellen können, wird der Standardwert operator= vom Compiler bereitgestellt, der alle benutzerdefinierten überlädt. In Ihrem Fall ruft es die Base::operator= (Base const&) vor dem Kopieren der abgeleiteten Mitglieder auf.

Überprüfen Sie diese link für Details zu Operator = virtuell gemacht werden.

5

Es gibt drei Operator = in diesem Fall:

Base::operator=(Base const&) // virtual 
Derived::operator=(Base const&) // virtual 
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly 

Dies erklärt, warum es sieht aus wie Basis :: operator = (Basis const &) ist "virtuell" im Fall genannt [1]. Es wird von der Compiler-generierten Version aufgerufen. Gleiches gilt für den Fall [3]. Im Fall 2 hat das Argument der rechten Seite 'bb' den Typ Basis &, daher kann Abgeleitet :: operator = (Abgeleitet &) nicht aufgerufen werden.

2

Der Grund dafür ist Compiler zur Verfügung gestellt Standardzuweisung operator=. Dies wird im Szenario a = b aufgerufen und wie wir wissen, ruft intern der Basiszuweisungsoperator auf.

Mehr Erklärung über virtuelle Zuordnung finden Sie unter: https://stackoverflow.com/a/26906275/3235055

Verwandte Themen