2009-06-09 4 views
21

En jouant avec l'implémentation d'un opérateur d'assignation virtuelle, j'ai terminé avec un comportement amusant. Ce n'est pas un problème de compilateur, puisque g ++ 4.1, 4.3 et VS 2005 partagent le même comportement. Fondamentalement, l'opérateur virtuel = se comporte différemment de toute autre fonction virtuelle par rapport au code qui est en cours d'exécution.Pourquoi l'affectation virtuelle se comporte-t-elle différemment des autres fonctions virtuelles de la même signature?

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 &) 
} 

L'effet est que l'opérateur virtuel = a un comportement différent de toute autre fonction virtuelle avec la même signature ([0] par rapport à [1]), en appelant la version de base de l'opérateur lorsqu'il est appelé par real Derived objects ([1]) ou Derived references ([3]) alors qu'il fonctionne comme une fonction virtuelle régulière lorsqu'il est appelé via les références de base ([2]), ou lorsque lvalue ou rvalue sont des références de base et l'autre un Référence dérivée ([4], [5]).

Y at-il une explication raisonnable à ce comportement étrange?

Répondre

13

Voilà comment ça se passe:

Si je change [1] pour

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

alors les choses fonctionnent comme prévu. Il y a un opérateur d'affectation généré automatiquement Derived qui ressemble à ceci:

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; 
} 

Dans votre exemple compilateurs ont assez d'informations pour deviner que a et b sont de type Derived et donc ils choisissent d'utiliser l'opérateur généré automatiquement au-dessus qui appelle le tiens. Voilà comment vous avez eu [1]. Mon pointeur force les compilateurs à le faire à votre façon, car je dis au compilateur d'oublier que b est de type Derived et utilise donc Base.

D'autres résultats peuvent être expliqués de la même manière.

+3

Il n'y a pas de deviner en cause ici. Les règles sont très strictes. – MSalters

+0

Merci, La vraie réponse (telle que publiée par déjà trois personnes) est que le compilateur généré opérateur = pour la classe Derived appelle implicitement l'opérateur Base :: operator =. Je marque ceci comme «réponse acceptée» comme c'était le premier. –

+0

'a = static_cast (b);' serait un moyen d'éviter les lancers de type C (qui risquent de faire accidentellement une réinterprétation) –

4

Aucun opérateur d'affectation fourni par l'utilisateur n'est défini pour la classe dérivée. Par conséquent, le compilateur synthétise un opérateur d'affectation de classe de base interne et est appelé à partir de cet opérateur d'affectation synthétisé pour la classe dérivée.

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

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

En classe dérivée, l'opérateur d'affectation de classe de base a été ignorée et, par conséquent, la méthode surchargée obtient une entrée dans la table virtuelle de la classe dérivée. Lorsque la méthode est invoquée via une référence ou des pointeurs, la méthode dérivée de classe Derived est appelée en raison de la résolution de l'entrée VTable au moment de l'exécution.

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

==> interne ==> (Object-> VTable [opérateur Assignation]) Obtenez l'entrée pour l'opérateur d'affectation dans VTable de la classe à laquelle appartient l'objet et invoquer la méthode.

3

Si vous ne parvenez pas à fournir un operator= approprié (c'est-à-dire des types de retour et d'argument corrects), le operator= par défaut est fourni par le compilateur qui surcharge toute définition définie par l'utilisateur. Dans votre cas, il appellera le Base::operator= (Base const&) avant de copier les membres dérivés.

Cochez cette case link pour plus de détails sur l'opérateur = rendu virtuel.

5

Il y a trois opérateur = dans ce cas:

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

Cela explique pourquoi il ressemble à la base :: operator = (Base const &) est appelé en cas "virtuellement" [1]. Il est appelé à partir de la version générée par le compilateur. La même chose s'applique au cas [3]. Dans le cas 2, l'argument de côté droit 'bb' a le type Base &, donc Derived :: operator = (Derived &) ne peut pas être appelé.

2

La raison en est l'attribution par défaut fournie par le compilateur operator=. Qui est appelé dans le scénario a = b et comme nous le savons par défaut appelle en interne l'opérateur d'affectation de base.

Plus d'explications sur l'affectation virtuelle se trouve à: https://stackoverflow.com/a/26906275/3235055

Questions connexes