2010-01-30 18 views
10

EDIT: RISOLTOPure metodo virtuale chiamato

Sto lavorando su un progetto multi-threaded in questo momento in cui ho una classe lavoratrice di base, con diversi classi operaie che ereditano da essa. Al runtime, le classi worker diventano thread, che quindi eseguono il lavoro secondo necessità.

ora, ho un direttore che ho scritto che si suppone di mantenere un array di puntatori a tutti i lavoratori, in modo che possa recuperare le informazioni da loro, così come modificare le variabili all'interno di in un secondo momento.

Ho fatto questo con la creazione di un puntatore a un puntatore della classe base:

baseWorkerClass** workerPtrArray; 

Poi nel costruttore del direttore, ho allocare dinamicamente un array di puntatori alla classe lavoratrice di base:

workerPtrArray = new baseWorkerClass*[numWorkers]; 

Nel costruttore di ciascun thread di lavoro, il worker chiama una funzione nel director che è destinata a memorizzare il puntatore di quel worker nella matrice.

Ecco come il regista memorizza i puntatori:

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

Ecco un esempio di una variante dei lavoratori. Ogni lavoratore eredita dalla classe worker di base e la classe worker di base contiene pure funzioni virtuali che dovrebbero esistere in tutte le varianti di worker, nonché alcune variabili condivise tra tutti i worker.

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 
}; 

Poi il baseWorkerClass sembra qualcosa di simile:

class baseWorkerClass 
{ 
public: 

    baseWorkerClass() 
    { 
    } 

    ~baseWorkerClass() 
    { 
    } 

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

Dopo ogni variante lavoratore è fatto l'inizializzazione, dovrei finire con un array di puntatori a oggetti baseWorkerClass. Ciò significa che dovrebbe essere in grado, ad esempio, ottenere il valore di una data variabile in un certo lavoratore utilizzando il suo ID come indice alla matrice, in questo modo:

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

Il problema è che questo codice provoca un incidente in un eseguibile di Windows, senza alcuna spiegazione del perché, e in Linux, si dice:

metodo virtuale puro chiamato
terminano chiamato senza eccezione attiva
Interrotto

Potrei giurare di averlo fatto funzionare a un certo punto, quindi sono confuso su cosa ho rovinato.


codice non modificato effettivo che ha il problema:

Worker variante intestazione: http://pastebin.com/f4bb055c8
Worker file sorgente variante: http://pastebin.com/f25c9e9e3

Base classe operaia intestazione: http://pastebin.com/f2effac5
Base file di lavoratore sorgente della classe: http://pastebin.com/f3506095b

Intestazione direttore: http://pastebin.com/f6ab1767a
Direttore file sorgente: http://pastebin.com/f5f460aae


EDIT: Informazioni aggiuntive, nella funzione manageWorker, mi può chiamare una delle funzioni virtuali pure dal puntatore "lavoratore", e funziona bene. Al di fuori della funzione manageWorker, quando provo a usare l'array pointer, fallisce.

EDIT: Ora che ci penso, il punto di ingresso del thread è operator(). Il thread Director viene creato prima dei worker, il che può significare che l'operatore parentesi sovraccarico chiama le funzioni pure virtuali prima che possano essere sovrascritte dalle classi child. Sto esaminando questo.

risposta

12

Il problema sembra essere che Director::manageWorker è chiamato nel costruttore di workerVariant casi:

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

Presumibilmente getThreadID() non è una funzione virtuale pura o si sarebbe ottenuto un errore di compilazione di non (si spera!) sovrascrivendolo in workerVariant. Ma getThreadID() potrebbe chiamare altre funzioni che dovresti sovrascrivere, ma vengono invocate nella classe astratta. Dovresti ricontrollare la definizione di getThreadID() per assicurarti di non fare nulla che possa dipendere dalla classe figlia prima che sia inizializzata correttamente.

Una soluzione migliore potrebbe essere quella di separare questo tipo di inizializzazione multistadio in un metodo separato o di progettare Director e baseWorkerClass in modo che non abbiano questo tipo di interdipendenza tra tempo di inizializzazione.

+0

Grazie per l'input. In realtà, riguardo al metodo getThreadID, era solo un'incongruenza da parte mia. Questo non è il codice che sto usando, sono solo cose che ho scritto pochi minuti fa per illustrare in modo chiaro e conciso il mio punto. il metodo getThreadID _is_ pure virtual, e _is_ sovrascritto in ogni variante worker. Tutto il metodo getThreadID restituisce la variabile "id" del thread. – jakogut

+13

Quando un oggetto è in costruzione (mentre il costruttore è eseguito), il meccanismo di funzione virtuale non chiamerà mai in funzioni virtuali di una classe derivata dal tipo dell'oggetto. Questo perché i costruttori di classi derivate devono ancora essere eseguiti.Quindi chiamare un metodo di una classe derivata da un costruttore non funzionerà. Una via d'uscita è la costruzione a due fasi, preferibilmente nascosta dietro un oggetto wrapper. (Lo stesso vale per la distruzione, BTW.) – sbi

+0

Oh, merda, hai ragione, non ci ho pensato. Fammi fare un test rapido qui, e ti riporto. (Passa un'altra ora di sonno.) – jakogut

2

La funzione virtuale pura chiamata indica che un membro che è puro nella classe base viene chiamato prima dell'avvio dell'esecuzione del costruttore del discendente. Nei programmi non multi-thread, questo significa direttamente o indirettamente nel costruttore della classe base. In un programma multi-thread, questo può accadere anche quando il costruttore avvia il thread nel costruttore e il sistema esegue il thread prima di aver terminato il costruttore.

2

Senza visualizzare il codice completo, azzarderei un'ipotesi che stai uscendo dal limite del blocco di memoria indicato da workerPtrArray. Avrebbe certamente senso dal momento che si lamenta della chiamata alla funzione virtuale pura. Se la memoria che viene sottoposta a dereferenziazione è spazzatura, allora il runtime non può dare un senso a tutto ciò e accadono cose strane.

Provare a mettere gli asserti in punti critici dove si sta dereferenziando l'array per assicurarsi che gli indici abbiano senso. Cioè limitare a 4 lavoratori e assicurarsi che l'ID sia inferiore a 4.

+0

Ora che ne parli, ricordo che il debugger di Visual Studio si lamentava degli accessi agli elementi dell'array che erano fuori limite. Non vedo perché questo sta accadendo però, perché ho controllato e ricontrollato i miei indici, e sono tutti entro i limiti. Inserirò il mio codice effettivo in pochi minuti. – jakogut

+1

Si potrebbe provare a utilizzare uno std :: vector anziché il doppio puntatore. Devo sempre cercare la "nuova" sintassi per allocare array come quello. Inoltre, questo sarà un po 'più chiaro e ti allevia dalla gestione della matrice. –

2

Durante le classi di inizializzazione sono solo parzialmente costruite.In particolare, i costruttori devono essere eseguiti dalla classe più antenata, in modo che ogni costruttore di classi derivate possa accedere in sicurezza ai suoi membri di base.

Ciò significa che il vtable di una classe parzialmente costruita non può essere nel suo stato finale - se i metodi virtuali fossero autorizzati a chiamare le classi derivate, quei metodi sarebbero chiamati prima che il costruttore di classi fosse stato chiamato.

Ciò significa che, durante la costruzione, le funzioni virtuali pure sono di fatto, puramente virtuali. I moderni compilatori C++ stanno migliorando la cattura, ma è possibile in molti casi "seppellire" la chiamata illegale in modo tale che il compilatore non noti l'errore.

Morale della trama: non fare nulla nel costruttore che invocherà una funzione virtuale. Semplicemente non farà quello che ti aspetti. Anche quando non è puro.

1

Non ho visto la classe variante costruita in nessuno dei vostri esempi di codice. Sei sicuro che l'id che viene passato rientra nell'intervallo per l'array worker? Inoltre, stai costruendo gli oggetti usando 'nuovo', giusto? Se hai costruito l'oggetto nello stack, esso si sarebbe registrato con Director, ma dopo che il costruttore ha restituito l'oggetto sarà immediatamente distrutto, ma il Director manterrà il suo puntatore all'oggetto che era nello stack.

Inoltre, il distruttore baseWorkerClass deve essere virtuale insieme al distruttore workerVariant per assicurarsi che vengano richiamati quando si elimina la matrice di baseWorkerClass.

Dal mio commento ad un'altra domanda, considera l'uso di std :: vector invece del doppio puntatore. È più semplice mantenere e comprendere e rimuovere la necessità di mantenere l'array.

Sembra che tu abbia aggiunto uno strato non necessario di astrazione qui. Non penso che l'id dovrebbe davvero essere parte dell'interfaccia della sottoclasse. Penso che qualcosa di simile potrebbe funzionare meglio per voi:

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

non sei per caso accedere agli oggetti dopo che sono stati distrutti? Perché durante la distruzione i puntatori vtable vengono gradualmente "ruotati indietro" in modo che le voci vtable puntino a metodi della classe base, alcuni dei quali sono astratti. Dopo aver cancellato l'oggetto, la memoria potrebbe essere lasciata com'era durante il distruttore della classe base.

Suggerisco di provare gli strumenti di debug della memoria, come ad esempio valgrind o MALLOC_CHECK_=2. Anche su Unix è abbastanza facile ottenere uno stacktrace per errori fatali. Basta eseguire la tua applicazione sotto gdb, o TotalView, e nel momento in cui si verifica l'errore si fermerà automaticamente, e potrai guardare lo stack.

0

ho ricevuto questo messaggio di errore una volta, e anche se non riguarda caso esatto del richiedente, che aggiunge questo nella speranza che possa essere utile ad altri:

ho risolto il problema facendo un generazione pulita .

Problemi correlati