que - polimorfismo puro c++
¿Por qué la asignación virtual se comporta de manera diferente a otras funciones virtuales de la misma firma? (5)
Así es como va:
Si cambio [1] a
a = *((Base*)&b);
Entonces las cosas funcionan como esperas. Hay un operador de asignación generado automáticamente en Derived
que se ve así:
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, los compiladores tienen suficiente información para adivinar que a
y b
son del tipo Derived
y, por lo tanto, optan por utilizar el operador generado automáticamente que llama al suyo. Así es como tienes [1]. Mi lanzamiento de puntero obliga a los compiladores a hacerlo a tu manera, porque le digo al compilador que "olvide" que b
es de tipo Derived
y por eso usa Base
.
Otros resultados pueden explicarse de la misma manera.
Mientras jugaba con la implementación de un operador de asignación virtual, he terminado con un comportamiento divertido. No es un problema técnico del compilador, ya que g ++ 4.1, 4.3 y VS 2005 comparten el mismo comportamiento.
Básicamente, el operador virtual = se comporta de manera diferente a cualquier otra función virtual con respecto al código que se está ejecutando realmente.
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 al de cualquier otra función virtual con la misma firma ([0] en comparación con [1]), al llamar a la versión Base del operador cuando se le llama a través de objetos Derivados reales ([1] ) o Referencias derivadas ([3]) mientras se realiza como una función virtual regular cuando se llama a través de Referencias Base ([2]), o cuando el valor l o rvalue son referencias Base y la otra una referencia Derivada ([4], [5]).
¿Hay alguna explicación sensata a este extraño comportamiento?
El motivo por el que está allí es que el compilador proporcionó el operator=
asignación predeterminado operator=
. Lo que se llama en el escenario a = b
como sabemos, se llama internamente al operador de asignación de base.
Puede encontrar más explicación sobre la asignación virtual en: https://.com/a/26906275/3235055
Hay tres operadores = 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 parece a Base :: operator = (Base const &) 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 Derived :: operator = (Derived &) no se puede llamar.
No hay un operador de asignación proporcionado por el usuario definido para la clase Derivada. Por lo tanto, el compilador sintetiza un operador de asignación de clase base interna y se llama 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 &)
a = b; // [1] outputs: Base::operator=(Base const &)
En la clase Derivada, el operador de asignación de la clase Base se ha anulado y, por lo tanto, el método anulado obtiene una entrada en la tabla virtual de la clase Derivada. Cuando el método se invoca a través de referencias o punteros, se llama al método derivado de la clase Derivada debido a la resolución de entrada de VTable en tiempo de ejecución.
ba = bb; // [2] outputs: Derived::operator=(Base const &)
==> internally ==> (Object-> VTable [Operador de asignación]) Obtenga la entrada para operador de asignación en VTable de la clase a la que pertenece el objeto e invoque el método.
Si no proporciona un operator=
apropiado operator=
(es decir, tipos de retorno y de argumento correctos), el compilador proporciona el operator=
predeterminado operator=
que sobrecarga a cualquier operator=
definido por el usuario. En su caso, llamará a Base::operator= (Base const& )
antes de copiar los miembros derivados.
Compruebe este link para obtener más información sobre el operador = se está haciendo virtual.