2008-09-19 9 views

Respuesta

92

Se pueden producir si intenta realizar una llamada de función virtual desde un constructor o un destructor. Como no puede realizar una llamada de función virtual desde un constructor o destructor (el objeto de clase derivado no se ha construido o ya se ha destruido), llama a la versión de clase base, que en el caso de una función virtual pura, doesn existo

(Ver demostración en vivo here)

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 
} 
+2

¿Alguna razón por la cual el compilador no pudo detectar esto, en general? – Thomas

+0

No veo ninguna razón técnica por la cual el compilador no pudo detectar esto. –

+1

GCC me da una advertencia solamente: test.cpp: En el constructor 'Base :: Base()': test.cpp: 4: advertencia: virtual virtual 'virtual void Base :: doIt()' llamado desde el constructor Pero falla en el momento del enlace. – Thomas

0

Supongo que hay un vtbl creado para la clase abstracta por algún motivo interno (puede 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 recibe. Es un error. Solo eso debería decir que algo que no puede suceder es.

Pura especulación

edición: Parece que estoy mal en el caso de que se trate. OTOH IIRC algunos lenguajes permiten llamadas vtbl desde el constructor destructor.

+0

No es un error en el compilador, si eso es lo que quiere decir. – Thomas

+0

Su sospecha es correcta: C# y Java lo permiten. En esos idiomas, los objetos en construcción tienen su tipo final. En C++, los objetos cambian de tipo durante la construcción y por eso y cuando puede tener objetos con un tipo abstracto. – MSalters

+0

* TODAS las clases abstractas, y los objetos reales creados derivados de ellas, necesitan una vtbl (tabla de funciones virtuales), enumerando las funciones virtuales a las que se debe llamar. En C++, un objeto es responsable de crear sus propios miembros, incluida la tabla de funciones virtuales. Los constructores son llamados desde la clase base a los derivados, y los destructores son llamados desde la derivada a la clase base, por lo que en una clase base abstracta, la tabla de funciones virtuales aún no está disponible. – fuzzyTew

7

lo general, cuando se llama a una función virtual a través de una referencia colgante - más probable es que la instancia ya ha sido destruida.

También puede haber más razones "creativas": tal vez ha 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.

-1

Aquí hay una manera furtiva de que suceda. Esto me pasó esencialmente 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(); 
+1

Por lo menos no se puede reproducir en mi VC2008, VPTR sí apunta a vtable de A cuando se inicializa por primera vez en el contructor de A, pero entonces cuando B está totalmente inicializado, VPTR se cambia para que apunte a vtable de B, lo cual está bien –

+0

coudnt reproducirla, ya sea con VS2010/12 – makc

+0

* 'me había ocurrido esto en esencia me today' *, obviamente, no es cierto, porque simplemente errónea: una función virtual pura se llama sólo cuando' callFoo() 'se llama dentro de un constructor (o destructor) , porque en este momento el objeto está quieto (o ya) en una etapa. [Aquí hay una versión en ejecución] (https://ideone.com/5zi4Kc) de su código sin el error de sintaxis en 'B b();' - los paréntesis lo convierten en una declaración de función, usted quiere un objeto. – Wolf

60

Al igual que el caso estándar de llamar a una función virtual desde el constructor o destructor de un objeto con funciones virtuales puras también se puede obtener una llamada de función virtual pura (en MSVC al menos) si se llama a un Portal función después de que el objeto ha sido destruido. Obviamente, esto es bastante malo intentarlo, pero si trabajas con clases abstractas como interfaces y te equivocas, entonces es algo que podrías ver. Posiblemente sea más probable si está usando interfaces contadas de referencia y tiene un error de recuento de ref o si tiene una condición de carrera de uso de objeto/destrucción de objeto en un programa de subprocesos múltiples ... Lo que pasa con estos tipos de llamadas puros es que es a menudo es menos fácil comprender lo que está sucediendo, ya que un cheque para los "sospechosos habituales" de llamadas virtuales en Ctor y Dtor saldrá limpio.

Para ayudar a depurar este tipo de problemas, puede, en diversas versiones de MSVC, reemplazar el controlador purecall de la biblioteca en tiempo de ejecución. Para ello, debe proporcionar su propia función con esta firma:

int __cdecl _purecall(void) 

y vincularla antes de vincular la biblioteca de tiempo de ejecución. Esto le da a USTED control de lo que ocurre 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 rastro de pila de donde ocurrió el llamado puro; 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).

+0

Gracias por el puntero sobre obtener una invocación _purecall() en una instancia eliminada; No era consciente de eso, pero solo lo probé con un pequeño código de prueba. Al mirar un vertedero post mortem en WinDbg, pensé que estaba lidiando con una raza donde otro hilo estaba tratando de usar un objeto derivado antes de que se hubiera construido por completo, pero esto arroja una nueva luz sobre el tema y parece ajustarse mejor a la evidencia. –

+0

Otra cosa voy a añadir: el '_purecall()' invocación que normalmente se produce en llamar a un método de una instancia de borrado _no_ suceder si la clase base se ha declarado con el '__declspec (novtable)' optimización (Microsoft específico) . Con eso, es completamente posible llamar a un método virtual anulado después de que el objeto ha sido eliminado, lo que podría enmascarar el problema hasta que lo muerda de alguna otra forma. La trampa '_purecall()' es tu amiga! –

+0

Eso es útil para conocer a Dave. Recientemente he visto algunas situaciones en las que no recibía mensajes puros cuando pensaba que debería estarlo. Quizás estaba cayendo en desgracia de esa optimización. –

0

Uso VS2010 y cada vez que intento llamar al destructor directamente desde el método público, aparece 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 se trasladó lo que hay dentro ~ Foo() para separar método privado, entonces funcionó a las mil maravillas.

template <typename T> 
class Foo { 
public: 
    Foo<T>() {}; 
    ~Foo<T>() {}; 

public: 
    void _MethodThatDestructs() {}; 
    void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */ 
}; 
Cuestiones relacionadas