2009-06-09 10 views
21

Mientras jugaba con la implementación de un operador de asignación virtual, terminé con un comportamiento extraño. No es un error del compilador, ya que g ++ 4.1, 4.3 y VS 2005 comparten el mismo comportamiento.¿Por qué la asignación virtual se comporta de manera diferente que otras funciones virtuales de la misma firma?

Básicamente, el operador virtual = se comporta de forma diferente que cualquier otra función virtual con respecto al código que se está ejecutando.

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

El efecto es que el operador virtual = tiene un comportamiento diferente de cualquier otra función virtual con la misma firma ([0] en comparación con [1]), llamando a la versión base del operador cuando se llama a través objetos Derivados reales ([1]) o Referencias derivadas ([3]) mientras funciona como una función virtual regular cuando se llama a través de referencias Base ([2]), o cuando el valor l o el valor r son referencias Base y el otro Referencia derivada ([4], [5]).

¿Hay alguna explicación sensata para este comportamiento extraño?

Respuesta

13

Así es como va:

Si cambio [1] a

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

entonces las cosas funcionan de la forma esperada. Hay un operador de asignación generada automáticamente en Derived que tiene este aspecto:

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

en su ejemplo compiladores tienen suficiente información para adivinar que a y b son de tipo Derived y por lo que optan por utilizar el operador generado automáticamente por encima de la llama tuya. Así es como tienes [1]. Mi puntero de fundición obliga a los compiladores a hacerlo a su manera, porque le digo al compilador que "olvide" que b es del tipo Derived y entonces usa Base.

Otros resultados pueden explicarse de la misma manera.

+3

No hay adivinando involucrado aquí. Las reglas son muy estrictas. – MSalters

+0

Gracias. La respuesta real (tal como la publicaron tres personas) es que el operador generado por el compilador = para la clase Derivada llama implícitamente a Base :: operator =. Estoy marcando esto como 'respuesta aceptada' ya que fue el primero. –

+0

'a = static_cast (b);» sería una forma de evitar los moldes de estilo C (que conllevan el riesgo de realizar accidentalmente un molde de reinterpretación) –

4

No hay un operador de asignación proporcionado por el usuario definido para la clase derivada. Por lo tanto, el compilador sintetiza uno y internamente el operador de asignación de clase base es llamado desde ese operador de asignación sintetizado para la clase Derivada.

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

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

En clase derivada, el operador de asignación de clase Base se ha reemplazado y por lo tanto, el método reemplazado consigue una entrada en la tabla virtual de la clase derivada. Cuando se invoca el método a través de referencias o punteros, se llama al método reemplazado de la clase derivada debido a la resolución de la entrada de VTable en tiempo de ejecución.

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

==> internamente ==> (Objeto-> Vtable [operador ASIGNACIÓN]) Obtener la entrada de operador de asignación en Vtable de la clase a la que pertenece el objeto e invocar el método.

3

Si no proporciona un operator= apropiado (es decir, tipos correctos de devolución y argumento), el compilador proporciona el valor predeterminado operator=, que sobrecarga uno definido por el usuario. En su caso, llamará al Base::operator= (Base const&) antes de copiar los miembros Derivados.

Compruebe esto link para detalles sobre operator = virtual.

5

Hay tres operador = en este caso:

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

Esto explica por qué se ve como Base operador :: = (const Base &) se llama "virtualmente" en el caso [1]. Se llama desde la versión generada por el compilador. Lo mismo se aplica al caso [3]. En el caso 2, el argumento del lado derecho 'bb' tiene el tipo Base &, por lo que no se puede invocar Derived :: operator = (Derivado &).

2

El motivo es que hay una asignación predeterminada proporcionada por el compilador operator=. que se llama en el escenario a = b y como conocemos por defecto, internamente llama al operador de asignación de base.

Más explicación sobre la asignación virtual se puede encontrar en: https://stackoverflow.com/a/26906275/3235055

Cuestiones relacionadas