2009-06-09 15 views
21

Mentre giocavo con l'implementazione di un operatore di assegnamento virtuale, mi sono concluso con un comportamento divertente. Non è un errore del compilatore, dal momento che g ++ 4.1, 4.3 e VS 2005 condividono lo stesso comportamento.Perché l'assegnazione virtuale si comporta in modo diverso rispetto ad altre funzioni virtuali della stessa firma?

In sostanza, l'operatore virtuale = si comporta in modo diverso rispetto a qualsiasi altra funzione virtuale rispetto al codice che viene effettivamente eseguito.

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'effetto è che l'operatore virtuale = ha un comportamento diverso da qualsiasi altra funzione virtuale con la stessa firma ([0] rispetto a [1]), chiamando la versione base dell'operatore quando chiamate tramite oggetti derivati ​​reali ([1]) o riferimenti derivati ​​([3]) mentre si comporta come una normale funzione virtuale quando chiamata attraverso i riferimenti Base ([2]), o quando il lvalue o il valore di riferimento sono riferimenti Base e l'altro a Riferimento derivato ([4], [5]).

C'è qualche spiegazione ragionevole a questo strano comportamento?

risposta

13

Ecco come va:

Se cambio [1] per

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

poi le cose funzionano nel modo previsto. C'è un operatore di assegnamento generato automaticamente in Derived che assomiglia a questo:

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

nel tuo esempio compilatori hanno abbastanza informazioni per intuire che a e b sono di tipo Derived e quindi scelgono di utilizzare l'operatore generato automaticamente sopra che chiama il tuo. Ecco come hai ottenuto [1]. Il cast del mio puntatore obbliga i compilatori a farlo a modo tuo, perché dico al compilatore di "dimenticare" che b è di tipo Derived e quindi utilizza Base.

Altri risultati possono essere spiegati allo stesso modo.

+3

Non c'è indovinare coinvolti qui. Le regole sono molto severe. – MSalters

+0

Grazie, la risposta reale (come postata da tre persone) è che il compilatore ha generato l'operatore = per la classe Derivata chiama implicitamente Base :: operator =. La segnalo come "risposta accettata" come se fosse la prima. –

+0

'a = static_cast (b);' sarebbe un modo per evitare i cast in stile C (che comportano il rischio di fare un cast reinterpretato) –

4

Non esiste un operatore di assegnazione fornito dall'utente definito per la classe derivata. Quindi, il compilatore sintetizza uno e internamente l'operatore di assegnazione di classe base viene chiamato da quell'operatore di assegnazione sintetizzato per la classe derivata.

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

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

In classe derivata, l'operatore di assegnazione Classe base è stato sostituito e quindi, il metodo override ottiene una voce nella tabella virtuale della classe derivata. Quando il metodo viene invocato tramite riferimento o puntatori, il metodo di override della classe Derived viene chiamato a causa della risoluzione della voce VTable in fase di esecuzione.

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

==> internamente ==> (Object-> VTable [operatore Incarico]) Ottenere la voce operatore di assegnazione in VTable della classe a cui appartiene l'oggetto e richiamare il metodo.

3

Se non si fornisce un valore appropriato per operator= (ovvero tipi di ritorno e argomento corretti), il compilatore predefinito fornisce il valore predefinito operator= che sovraccarica qualsiasi utente definito. Nel tuo caso chiamerà lo Base::operator= (Base const&) prima di copiare i membri derivati.

Controllare questo link per i dettagli sull'operatore = essere reso virtuale.

5

ci sono tre operatore = in questo caso:

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

Questo spiega il motivo per cui sembra Base :: operator = (const Base &) è chiamato "virtualmente" nel caso in cui [1]. Si chiama dalla versione generata dal compilatore. Lo stesso vale per il caso [3]. Nel caso 2, l'argomento di destra "bb" ha tipo Base &, quindi Derived :: operator = (Derivato &) non può essere chiamato.

2

Il motivo per cui è disponibile il compilatore è l'assegnazione predefinita operator=. Quale viene chiamato nello scenario a = b e come sappiamo internamente chiama l'operatore di assegnazione base.

ulteriori spiegazioni in merito assegnazione virtuale sono disponibili all'indirizzo: https://stackoverflow.com/a/26906275/3235055

Problemi correlati