A veces noto programas que se bloquean en mi computadora con el error: "llamada de función virtual pura".
¿Cómo se compilan estos programas cuando no se puede crear un objeto a partir de una clase abstracta?
A veces noto programas que se bloquean en mi computadora con el error: "llamada de función virtual pura".
¿Cómo se compilan estos programas cuando no se puede crear un objeto a partir de una clase abstracta?
Respuestas:
Pueden resultar si intenta realizar una llamada de función virtual desde un constructor o destructor. Dado que no puede realizar una llamada de función virtual desde un constructor o destructor (el objeto de clase derivada no se ha construido o ya ha sido destruido), llama a la versión de la clase base, que en el caso de una función virtual pura, no no existe.
(Ver demostración en vivo aquí )
class Base
{
public:
Base() { doIt(); } // DON'T DO THIS
virtual void doIt() = 0;
};
void Base::doIt()
{
std::cout<<"Is it fine to call pure virtual function from constructor?";
}
class Derived : public Base
{
void doIt() {}
};
int main(void)
{
Derived d; // This will cause "pure virtual function call" error
}
doIt()
llamada en el constructor se desvirtualiza fácilmente y se envía de Base::doIt()
forma estática, lo que solo provoca un error del enlazador. Lo que realmente necesitamos es una situación en la que el tipo dinámico durante un envío dinámico sea el tipo base abstracto.
Base::Base
llamar a un no virtual f()
que a su vez llama al doIt
método virtual (puro) .
Además del caso estándar de llamar a una función virtual desde el constructor o destructor de un objeto con funciones virtuales puras, también puede obtener una llamada a función virtual pura (al menos en MSVC) si llama a una función virtual después de que el objeto ha sido destruido . Obviamente, esto es algo bastante malo de intentar y hacer, pero si está trabajando con clases abstractas como interfaces y se equivoca, entonces es algo que puede ver. Es posible que sea más probable si está utilizando interfaces contadas referenciadas y tiene un error de recuento de referencias o si tiene una condición de carrera de uso / destrucción de objetos en un programa de subprocesos múltiples ... Lo que pasa con este tipo de purecall es que es A menudo, es menos fácil comprender lo que está sucediendo, ya que una verificación de los "sospechosos habituales" de las llamadas virtuales en ctor y dtor saldrá limpio.
Para ayudar con la depuración de este tipo de problemas, en varias versiones de MSVC, puede reemplazar el controlador purecall de la biblioteca en tiempo de ejecución. Para ello, proporcione su propia función con esta firma:
int __cdecl _purecall(void)
y vincularlo antes de vincular la biblioteca en tiempo de ejecución. Esto le da a USTED el control de lo que sucede cuando se detecta una llamada pura. Una vez que tenga el control, puede hacer algo más útil que el controlador estándar. Tengo un controlador que puede proporcionar un seguimiento de la pila de dónde ocurrió la llamada pura; consulte aquí: http://www.lenholgate.com/blog/2006/01/purecall.html para obtener más detalles.
(Tenga en cuenta que también puede llamar a _set_purecall_handler () para instalar su controlador en algunas versiones de MSVC).
_purecall()
invocación que normalmente ocurre al llamar a un método de una instancia eliminada no ocurrirá si la clase base ha sido declarada con la __declspec(novtable)
optimización (específica de Microsoft). Con eso, es completamente posible llamar a un método virtual anulado después de que se haya eliminado el objeto, lo que podría enmascarar el problema hasta que lo muerda de alguna otra forma. ¡La _purecall()
trampa es tu amiga!
Por lo general, cuando llama a una función virtual a través de un puntero colgante, lo más probable es que la instancia ya haya sido destruida.
También puede haber razones más "creativas": tal vez haya logrado cortar la parte de su objeto donde se implementó la función virtual. Pero generalmente es solo que la instancia ya ha sido destruida.
Me encontré con el escenario en el que se llama a las funciones virtuales puras debido a objetos destruidos, Len Holgate
ya tengo una respuesta muy agradable , me gustaría agregar algo de color con un ejemplo:
El destructor de la clase Derived restablece los puntos vptr a la clase Base vtable, que tiene la función virtual pura, por lo que cuando llamamos a la función virtual, en realidad llama a las virutales puras.
Esto podría suceder debido a un error de código obvio o un escenario complicado de condición de carrera en entornos de subprocesos múltiples.
Aquí hay un ejemplo simple (compilación de g ++ con la optimización desactivada; un programa simple podría optimizarse fácilmente):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
Y el seguimiento de la pila se ve así:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
Realce:
Si el objeto se elimina por completo, lo que significa que se llama al destructor y se recupera Memroy, es posible que simplemente obtengamos un mensaje Segmentation fault
porque la memoria ha regresado al sistema operativo y el programa simplemente no puede acceder a él. Por lo tanto, este escenario de "llamada de función virtual pura" generalmente ocurre cuando el objeto se asigna en el grupo de memoria, mientras que se elimina un objeto, la memoria subyacente en realidad no es reclamada por el sistema operativo, todavía está accesible por el proceso.
Supongo que hay un vtbl creado para la clase abstracta por alguna razón interna (podría ser necesario para algún tipo de información de tipo de tiempo de ejecución) y algo sale mal y un objeto real lo obtiene. Es un error. Solo eso debería decir que algo que no puede suceder es.
Pura especulación
editar: parece que estoy equivocado en el caso en cuestión. OTOH IIRC algunos lenguajes permiten llamadas vtbl fuera del constructor destructor.
Yo uso VS2010 y cada vez que intento llamar al destructor directamente desde el método público, obtengo un error de "llamada de función virtual pura" durante el tiempo de ejecución.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
Así que moví lo que hay dentro de ~ Foo () para separar el método privado, luego funcionó como un encanto.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
Si usa Borland / CodeGear / Embarcadero / Idera C ++ Builder, puede implementar
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
Durante la depuración, coloque un punto de interrupción en el código y vea la pila de llamadas en el IDE; de lo contrario, registre la pila de llamadas en su controlador de excepciones (o esa función) si tiene las herramientas adecuadas para ello. Yo personalmente uso MadExcept para eso.
PD. La llamada a la función original está en [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
Aquí hay una forma disimulada de que suceda. Esencialmente me pasó esto hoy.
class A
{
A *pThis;
public:
A()
: pThis(this)
{
}
void callFoo()
{
pThis->foo(); // call through the pThis ptr which was initialized in the constructor
}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual void foo()
{
}
};
B b();
b.callFoo();
I had this essentially happen to me today
obviamente no es cierto, porque simplemente es incorrecto: una función virtual pura se llama solo cuando callFoo()
se llama dentro de un constructor (o destructor), porque en este momento el objeto está todavía (o ya) en la etapa A. Aquí hay una versión en ejecución de su código sin el error de sintaxis B b();
: los paréntesis la convierten en una declaración de función, desea un objeto.