Pregunta

A veces noto programas que fallan en mi computadora con el error:"llamada a función virtual pura".

¿Cómo se compilan estos programas cuando no se puede crear un objeto a partir de una clase abstracta?

¿Fue útil?

Solución

Pueden surgir si intenta realizar una llamada a una función virtual desde un constructor o destructor.Como no se puede realizar una llamada a una función virtual desde un constructor o destructor (el objeto de clase derivada no se ha construido o ya se ha 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
}

Otros consejos

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 una función virtual pura (al menos en MSVC) si llama a una función virtual después de que el objeto haya sido destruido. .Obviamente, esto es algo bastante malo de intentar hacer, pero si estás trabajando con clases abstractas como interfaces y te equivocas, entonces es algo que podrías ver.Posiblemente sea más probable si está utilizando interfaces contadas referenciadas y tiene un error de conteo 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 llamadas puras es que a menudo es menos fácil comprender qué está pasando, ya que una verificación de los "sospechosos habituales" de las llamadas virtuales en ctor y dtor resultará limpia.

Para ayudar a depurar 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 control de lo que sucede cuando se detecta una llamada pura.Una vez que tengas el control, podrás 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;mira aquí: http://www.lenholgate.com/blog/2006/01/purecall.html para más detalles.

(Tenga en cuenta que también puede llamar a _set_purecall_handler() para instalar su controlador en algunas versiones de MSVC).

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 normalmente es sólo que la instancia ya ha sido destruida.

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.Sólo eso debería decir que algo que no puede suceder sí lo es.

Pura especulación

editar: Parece que estoy equivocado en el caso en cuestión.OTOH IIRC, algunos lenguajes permiten llamadas vtbl desde el destructor del constructor.

Utilizo VS2010 y cada vez que intento llamar al destructor directamente desde un método público, aparece un error de "llamada a 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ó de maravilla.

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, simplemente 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

Me encontré con el escenario en el que se llaman funciones virtuales puras debido a objetos destruidos, Len Holgate Ya tengo una respuesta muy buena, me gustaría agregar algo de color con un ejemplo:

  1. Se crea un objeto derivado, y el puntero (como clase base) se guarda en algún lugar
  2. El objeto derivado se elimina, pero de alguna manera todavía se hace referencia al puntero
  3. El puntero que apunta a un objeto derivado eliminado se llama

El destructor de clase derivada 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 a 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

Destacar:

Si el objeto se elimina por completo, lo que significa que se llama al destructor y se recupera la memoria, es posible que simplemente obtengamos un Segmentation fault ya que la memoria ha regresado al sistema operativo y el programa simplemente no puede acceder a ella.Por lo tanto, este escenario de "llamada a función virtual pura" generalmente ocurre cuando el objeto se asigna en el grupo de memoria, mientras se elimina un objeto, el sistema operativo no recupera la memoria subyacente, el proceso aún puede acceder a ella.

Aquí tienes una forma furtiva de que esto suceda.Básicamente, esto me pasó a mí 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();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top