2010-01-30 25 views
10

EDIT: RESOLVERmétodo virtual puro Llamado

Estoy trabajando en un proyecto multi-hilo en este momento en el que tengo una clase trabajadora de base, con diferentes clases de trabajadores que heredan de ella. En el tiempo de ejecución, las clases de trabajadores se convierten en hilos, que luego realizan el trabajo según sea necesario.

Ahora, tengo un Director que he escrito que se supone que debe mantener una serie de punteros a todos los trabajadores, para que pueda recuperar información de ellos, así como modificar las variables dentro de ellos más tarde.

me hicieron esto mediante la creación de un puntero a un puntero de la clase base:

baseWorkerClass** workerPtrArray; 

A continuación, en el constructor del Director, que dinámicamente asignar una matriz de punteros a la clase trabajadora de base:

workerPtrArray = new baseWorkerClass*[numWorkers]; 

En el constructor de cada subproceso de trabajador, el trabajador invoca una función en el director que debe almacenar el puntero de ese trabajador en la matriz.

Así es como el director almacena los punteros:

Director::manageWorker(baseWorkerClass* worker) 
{ 
    workerPtrArray[worker->getThreadID()] = worker; 
} 

Aquí es un ejemplo de una variante del trabajador. Cada trabajador hereda de la clase de trabajador base, y la clase de trabajador base contiene funciones virtuales puras que deberían existir en todas las variantes de trabajador, así como algunas variables que se comparten entre todos los trabajadores.

class workerVariant : protected baseWorkerClass 
{ 
    public: 

    workerVariant(int id) 
    : id(id) 
    { 
     Director::manageWorker(this); 
    } 

    ~workerVariant() 
    { 
    } 

    int getThreadID() 
    { 
     return id; 
    } 

    int getSomeVariable() 
    { 
     return someVariable; 
    } 

    protected: 

    int id; 
    int someVariable 
}; 

Entonces el baseWorkerClass se ve algo como esto:

class baseWorkerClass 
{ 
public: 

    baseWorkerClass() 
    { 
    } 

    ~baseWorkerClass() 
    { 
    } 

    virtual int getThreadID() = 0; 
    virtual int getSomeVariable() = 0; 
}; 

Después de cada variante trabajador se realiza la inicialización, debería terminar con una matriz de punteros a objetos baseWorkerClass. Esto significa que debería ser capaz de, por ejemplo, obtener el valor de una variable dada en un determinado trabajador utilizando su ID como el índice a la matriz, así:

workerPtrArray[5]->getSomeVariable(); // Get someVariable from worker thread 5 

El problema es que este código provoca una accidente en un ejecutable de Windows, sin ninguna explicación de por qué, y en Linux, que dice:

método virtual pura llamada
terminar el arrendamiento llama sin excepción activa
abortada

Podría haber jurado que tenía esto funcionando en algún momento, así que estoy confundido sobre lo que he estropeado.


código sin modificar real que tiene el problema:

cabecera de la variante Trabajador: http://pastebin.com/f4bb055c8
Trabajador archivo de origen variante: http://pastebin.com/f25c9e9e3

cabecera de la clase trabajadora Base: http://pastebin.com/f2effac5
Base archivo fuente de la clase trabajadora: http://pastebin.com/f3506095b

Encabezado del director: http://pastebin.com/f6ab1767a
archivo director Fuente: http://pastebin.com/f5f460aae


EDIT: La información adicional, en la función manageWorker, me puede llamar a cualquiera de las funciones virtuales puras desde el puntero del "trabajador", y funciona muy bien. Fuera de la función manageWorker, cuando intento usar la matriz de punteros, falla.

EDIT: Ahora que lo pienso, el punto de entrada del hilo es operator(). El subproceso de Director se crea antes que los trabajadores, lo que puede significar que el operador de paréntesis sobrecargado llama funciones virtuales puras antes de que las clases secundarias puedan anularlas. Estoy investigando esto.

Respuesta

12

El problema parece ser que Director::manageWorker se llama en el constructor de instancias: workerVariant

Director::manageWorker(baseWorkerClass* worker) { 
    workerPtrArray[worker->getThreadID()] = worker; 
} 

Presumiblemente getThreadID() no es una función virtual pura o se habría conseguido un error del compilador de no (ojalá!) anulándolo en workerVariant. Pero getThreadID() podría llamar a otras funciones que debe anular, pero se invocan en la clase abstracta. Debería verificar dos veces la definición de getThreadID() para asegurarse de no hacer nada inconveniente que dependería de la clase secundaria antes de que se inicialice correctamente.

Una mejor solución podría ser separar este tipo de inicialización de etapas múltiples en un método separado, o diseñar Director y baseWorkerClass de modo que no tengan este tipo de interdependencia de tiempo de inicialización.

+0

Gracias por la entrada. En realidad, sobre el método getThreadID, eso fue solo una inconsistencia de mi parte. Este no es el código real que estoy usando, solo son cosas que escribí hace unos minutos para ilustrar mi punto de forma clara y concisa. el método getThreadID _is_ pure virtual, y _is_ reemplazado en cada variante de trabajador. Todo lo que hace el método getThreadID es devolver la variable "id" del hilo. – jakogut

+13

Cuando un objeto está en construcción (mientras se ejecuta el constructor), el mecanismo de función virtual nunca llamará a las funciones virtuales de una clase derivada del tipo del objeto. Esto se debe a que los constructores de las clases derivadas aún no se han ejecutado.Entonces, llamar a un método de clase derivado desde un constructor no funcionará. Una salida de esto es la construcción en dos fases, preferiblemente escondida detrás de un objeto envoltorio. (Lo mismo vale para la destrucción, por cierto.) – sbi

+0

Oh, mierda, tienes razón, no pensé en eso. Déjame hacer una prueba rápida aquí, y te informaré. (Ahí va otra hora de sueño). – jakogut

2

La función virtual pura llamada significa que se llama a un miembro que es puro en la clase base antes de que se inicie la ejecución del constructor del descendiente. En programas no multihilo, esto significa directa o indirectamente en el constructor de la clase base. En un programa de subprocesos múltiples, esto también puede ocurrir cuando el constructor inicia el subproceso en el constructor y el sistema ejecuta el subproceso antes de terminar el constructor.

2

Sin ver el código completo, supongo que está saliendo del límite del bloque de memoria señalado por workerPtrArray. Sin duda tendría sentido, ya que se queja de la función virtual pura que se llama. Si la memoria desreferenciada es basura, el tiempo de ejecución no puede encontrarle sentido y ocurren cosas extrañas.

Intenta poner cierres en lugares críticos en los que eliminas la referencia de la matriz para asegurarte de que los índices tengan sentido. Es decir. limite a 4 trabajadores y asegúrese de que la identificación sea menor que 4.

+0

Ahora que lo menciona, recuerdo que el depurador de Visual Studio se queja de que los elementos de la matriz a los que se accedió estaban fuera de los límites. Sin embargo, no veo por qué sucede esto, porque he verificado y comprobado dos veces mis índices, y todos están dentro de los límites. Publicaré mi código real en unos minutos. – jakogut

+1

Puede intentar usar un std :: vector en lugar del doble puntero. Siempre tengo que buscar la sintaxis "nueva" para asignar matrices así. Además, esto será un poco más claro y lo aliviará de administrar la matriz. –

2

Durante la inicialización, las clases se construyen solo parcialmente.Específicamente, los constructores deben ejecutarse desde la clase más ancestral, de modo que cada constructor de clase derivado pueda acceder de forma segura a sus miembros base.

esto significa que el vtable de una clase parcialmente construida no puede estar en su estado final: si se permitiera que los métodos virtuales llamaran a clases derivadas, esos métodos se llamarían antes de que se llamara al constructor de clases.

Lo que significa que, durante la construcción, las funciones virtuales puras son, de hecho, virtuales puras. Los compiladores modernos de C++ son cada vez mejores para captar esto, pero es posible que en muchos casos "entierre" la llamada ilegal de tal manera que el compilador no advierta el error.

Moraleja de la historia: no hagas nada en tu constructor que invoque una función virtual. Simplemente no hará lo que esperas. Incluso cuando no es puro.

1

No vi la clase de variante que se está construyendo en ninguno de los ejemplos de código. ¿Estás seguro de que el ID que se pasa está dentro del alcance de la matriz de trabajadores? Además, estás construyendo los objetos usando 'nuevo', ¿verdad? Si construyes el objeto en la pila, se registraría con el Director, pero después de que el constructor lo devuelva, el objeto se destruirá inmediatamente, pero el Director retendrá su puntero al objeto que estaba en la pila.

Además, su destructor baseWorkerClass debe ser virtual junto con el destructor workerVariant para asegurarse de que se invoquen cuando elimine la matriz de baseWorkerClass.

De mi comentario a otra pregunta, considere usar std :: vector en lugar del doble puntero. Es más fácil de mantener y comprender, y elimina la necesidad de mantener la matriz.

Parece que ha agregado una capa innecesaria de abstracción aquí. No creo que la identificación realmente sea parte de la interfaz de la subclase. Creo que algo como esto podría funcionar mejor para usted:

class baseWorkerClass 
{ 
public: 

    baseWorkerClass(int id) : 
     id(id) 
    { 
    } 

    virtual ~baseWorkerClass() 
    { 
    } 

    int getThreadID(){ return id; }; 
    virtual int getSomeVariable() = 0; 

protected: 
    int id; 
}; 

class workerVariant : protected baseWorkerClass 
{ 
    public: 

    workerVariant(int id) : 
     baseWorkerClass(id) 
    { 
     Director::manageWorker(this); 
    } 

    virtual ~workerVariant() 
    { 
    } 

    int getSomeVariable() 
    { 
     return someVariable; 
    } 

protected: 
    int someVariable 
}; 
0

¿no es por casualidad acceder a los objetos después de que se destruyen? Porque durante la destrucción, los punteros vtable se "retrotraen" gradualmente, de modo que las entradas vtable apuntarán a los métodos de la clase base, algunos de los cuales son abstractos. Después de eliminar el objeto, la memoria podría dejarse como estaba durante el destructor de la clase base.

Sugiero que intente herramientas de depuración de memoria, como valgrind o MALLOC_CHECK_=2. También en Unix es bastante fácil obtener una stacktrace para tales errores fatales. Simplemente ejecute su aplicación en gdb, o TotalView, y en el punto en que ocurre el error se detendrá automáticamente, y puede mirar la pila.

0

Tengo este mensaje de error una vez, y aunque no se refiere al caso exacto del autor de la pregunta, añado esto con la esperanza de que podría ser útil a los demás:

me ha solucionado el problema al hacer una generación limpia .

Cuestiones relacionadas