2011-01-06 31 views
31

Enseño una clase de programación C++ y he visto suficientes clases de errores que tengo una buena idea de cómo diagnosticar errores comunes de C++. Sin embargo, hay un tipo importante de error para el cual mi intuición no es particularmente buena: ¿Qué errores de programación causan llamadas a funciones virtuales puras? El error más común que he visto que causa esto es llamar a una función virtual desde un destructor o constructor de clase base. ¿Hay algún otro que deba tener en cuenta a la hora de ayudar a depurar el código del alumno?¿Qué puede causar una llamada de función virtual pura en C++?

+0

otros quizás llamándolo desde algunas funciones miembro de la clase base, ¿qué más podría estar allí? pero eso no es un error! : | – Nawaz

+0

Eso es realmente lo que mi pregunta es. :-) Puede que no haya otra manera de activar una llamada virtual pura, y mi pregunta principal es si hay o no una. – templatetypedef

Respuesta

27

"El error más común que he visto que causa esto es llamar a una función virtual desde un destructor o constructor de clase base".

Cuando se construye un objeto, el puntero a la tabla de despacho virtual se dirige inicialmente a la superclase más alta, y solo se actualiza cuando las clases intermedias finalizan la construcción. Por lo tanto, puede llamar accidentalmente a la implementación virtual pura hasta el punto en que una subclase (con su propia implementación de funciones primordiales) complete la construcción. Esa podría ser la subclase más derivada, o cualquier punto intermedio.

Puede suceder si sigue un puntero a un objeto parcialmente construido (por ejemplo, en una condición de carrera debido a operaciones asincrónicas o roscadas).

Si un compilador tiene motivos para pensar que conoce el tipo real al que apunta una puntero-a-base-class, puede omitir razonablemente el despacho virtual. Puede confundirlo haciendo algo con un comportamiento indefinido, como un elenco de reinterpretación.

Durante la destrucción, la tabla de despacho virtual debe revertirse a medida que se destruyen las clases derivadas, por lo que puede invocarse nuevamente la implementación virtual pura.

Después de la destrucción, el uso continuado del objeto mediante punteros "colgantes" o referencias puede invocar la función virtual pura, pero no existe un comportamiento definido en tales situaciones.

+0

En realidad, las llamadas virtuales están deshabilitadas en constructores y destructores - [se explica aquí] (https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors) - por lo tanto, las llamadas no son virtuales y el enlazador error sucede. Cualquier envío no virtual provocará un error del enlazador. Pero estoy de acuerdo con los objetos parcialmente construidos (o corruptos) cuando la tabla virtual es simplemente incorrecta. Sin embargo, estas situaciones requieren trucos dinámicos que el compilador no ve (subprocesos como se menciona, reinterpretar_cast, etc.). – uvsmtid

+0

@uvsmtid: "En realidad" implica que estás corrigiendo algunas afirmaciones erróneas que hice, pero no mencioné constructores y destructores, solo * construcción * y * destrucción *. La construcción consiste en la inicialización de la clase base y de los miembros de datos no estáticos antes de que se procese la lista de inicialización y el cuerpo del constructor; es casi lo mismo a la inversa durante la destrucción. El envío a funciones virtuales puras puede suceder como se ilustra [aquí] (http://coliru.stacked-crooked.com/a/29b4fa463e39e277), sin enhebrar, async, 'reinterpret_cast' etc. –

+0

Por separado, desde el enlace que proporcione o de lo contrario, no puedo imaginar cómo se manifiestan los errores del enlazador. ¿Podría mostrarme algún código? Tal vez el sitio coliru al que me he vinculado en el comentario anterior, ideone.com o lo que prefiera ... Saludos –

8

Aquí hay algunos casos en los que puede ocurrir una llamada virtual pura.

  1. Usando una referencia colgante - el puntero no es de un objeto válido por lo que la tabla virtual al que apunta es sólo la memoria aleatoria que puede contener NULL
  2. Malo fundido utilizando un static_cast a la equivocada tipo (o molde de estilo C) también puede hacer que el objeto al que apunta no tenga los métodos correctos en su tabla virtual (en este caso al menos realmente es una tabla virtual a diferencia de la opción anterior).
  3. DLL se ha descargado - Si el objeto que está aferrándose a fue creado en un archivo objeto compartido (DLL, por lo que, SL), una vez descargado de nuevo la memoria puede ser llevado a cero ahora
+0

¿No son tres solo una manera de obtener uno? – GManNickG

+2

El controlador purecall suele ser una función especial, no NULA. – ephemient

+0

@GMan, más o menos sí, tal vez no se merece un punto aparte, pero es más fácil de diagnosticar que el caso general – Motti

0

Esto puede suceder, por ejemplo, cuando la referencia o el puntero a un objeto apunta a una ubicación NULA, y utiliza la referencia o el puntero del objeto para llamar a una función virtual en la clase. Por ejemplo:

std::vector <DerivedClass> objContainer; 
if (!objContainer.empty()) 
    const BaseClass& objRef = objContainer.front(); 
// Do some processing using objRef and then you erase the first 
// element of objContainer 
objContainer.erase(objContainer.begin()); 
const std::string& name = objRef.name(); 
// -> (name() is a pure virtual function in base class, 
// which has been implemented in DerivedClass). 

En este punto, el objeto almacenado en objContainer [0] no existe. Cuando la tabla virtual está indexada, no se encuentra una ubicación de memoria válida. Por lo tanto, se emite un error de tiempo de ejecución que dice "función virtual pura llamada".

+0

Sí, lo intenté y me dieron un error "se llamó un método virtual puro". ¿Qué error estás viendo? – cppcoder

+0

El ejemplo está incompleto, por lo que aún no lo reconstruí. Pero como supongo, está llamando a una función virtual para un puntero peligroso, lo intentaré pronto. Por cierto: este es un comportamiento indefinido, por lo que podría bloquearse de una manera diferente. – Wolf

Cuestiones relacionadas