Un caso muy común: tratar de llamar a un método virtual pura desde el constructor ...
constructores
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Ahora, supongamos que la siguiente aplicación de Interface()
Interface::Interface() {}
Entonces todo está bien:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
Es tan doloroso llamar puro al constructor, sería mejor factorizar el código ¿verdad?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
¡Entonces podemos hacerlo en una línea!
Concrete myConcrete; // CRASHES VIOLENTLY
¿Por qué?
Porque el objeto está construido de abajo hacia arriba. Veámoslo.
instrucciones para construir una clase Concrete
(más o menos)
asignar suficiente memoria (por supuesto), y suficiente memoria para la _vtable también (1 función de puntero por función virtual, por lo general en el orden en que se declaran , partiendo de la base de más a la izquierda)
llamada Concrete
constructor (el código que no se ve)
a> Call Interface
constructor, que inicializar el _vtabl E con sus punteros
b> Llamada cuerpo Interface
del constructor (que escribió eso)
c> Reemplaza los punteros en el _vtable en dichos métodos de anulación de hormigón
d> Llamada cuerpo Concrete
del constructor (que escribió que)
¿Cuál es el problema? Bueno, mira b>
y c>
orden;)
Cuando llamas a un método virtual
desde un constructor, no hace lo que estás esperando. Va a la _vtable para buscar el puntero, pero el _vtable
aún no se ha inicializado por completo. Así, por todo lo que importa, el efecto de:
D() { this->call(); }
es, de hecho:
D() { this->D::call(); }
Al llamar a un método virtual desde dentro de un constructor, no lo hace el tipo dinámico del objeto al construirse, tiene el tipo estático del Constructor actual invocado.
En mi ejemplo Interface
/Concrete
, significa Interface
tipo, y el método es virtual pura, por lo que el _vtable no se sostiene un puntero real (0x0 o 0x01 por ejemplo, si su compilador es lo suficientemente amable a los valores de depuración de configuración a Ayudamos allí).
destructores
Coincidentemente, vamos a examinar el caso Destructor;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
Así que lo que sucede en la destrucción? Bueno, el _vtable funciona bien, y se invoca el tipo de tiempo de ejecución real ... lo que significa aquí, sin embargo, es un comportamiento indefinido, porque ¿quién sabe qué pasó con m_data
después de haber sido eliminado y antes de invocar Interface
destructor?No lo hago;)
Conclusión
Nunca jamás llamar a los métodos virtuales desde el interior de constructores o destructores.
Si no es eso, uno se queda con una corrupción de memoria, mala suerte;)
Puede aclarar si el puntero de función en la viable es 0 o si es la de que este puntero es nulo? ¿O es un puntero en el objeto que apunta a la vtable actual? –
El puntero vtable es 1. El bloqueo se produce al intentar ejecutar 'call * 0x1c (% eax)' (en sintaxis at & t) y el valor de eax es 1 (no cero como dije incorrectamente). – Tobias
Ejecútelo en gdb, establezca un punto de observación en la dirección, vea qué lo está escribiendo. –