2015-07-17 12 views
7

Sto cercando di creare un oggetto service che può essere eseguito (ad esempio, eseguire la funzione run()) in un thread separato. Questo è l'oggetto del servizioEreditarietà C++: chiamata al metodo virtuale quando è stata sovrascritta

#include <boost/noncopyable.hpp> 
#include <atomic> 
#include <thread> 
#include <iostream> 

class service : public boost::noncopyable { 
public: 
    service() : stop_(false), started_(false) { } 

    virtual ~service() { 
    stop(); 
    if (thread_.joinable()) { 
     thread_.join(); 
    } 
    } 

    virtual void stop() { stop_ = true; } 

    virtual void start() { 
    if (started_.load() == false) { 
     started_ = true; 
     thread_ = std::thread([&]() { 
     run(); 
     }); 
    } 
    } 

protected: 
    virtual void run() = 0; 

    std::atomic<bool> stop_; 

    std::atomic<bool> started_; 

    std::thread thread_; 
}; 

Io sono la creazione di una classe test che eredita da questa classe astratta e si chiama nella funzione main()

class test : public service { 
public: 
    test() : service() { 
    std::cout<< "CTOR" << std::endl; 
    start(); 
    } 

    ~test() { 
    std::cout<< "DTOR" << std::endl; 
    } 

protected: 
    void run() override { 
    std::cout << "HELLO WORLD" <<std::endl; 
    } 
}; 


int main() { 
    test test1; 
    return 0; 
} 

Ora, quando eseguo questo, perché posso ottenere un errore dicendo pure virtual function called? La funzione run() è chiaramente sovrascritta nella classe test. Qual è il peggio è che funziona correttamente a volte?

$ ./a.out 
CTOR 
DTOR 
pure virtual method called 
terminate called without an active exception 

$ ./a.out 
CTOR 
DTOR 
pure virtual method called 
terminate called without an active exception 

$ ./a.out 
CTOR 
DTOR 
pure virtual method called 
terminate called without an active exception 

$ ./a.out 
CTOR 
DTOR 
HELLO WORLD 

$ ./a.out 
CTOR 
DTOR 
pure virtual method called 
terminate called without an active exception 

Cosa potrebbe andare storto qui?

+1

si sta chiamando la funzione virtuale 'start();' nel costruttore. Questa è la ragione di questo errore. – Jagannath

+2

@Jagannath Questo è perfettamente definito qui e non è un problema. – Barry

+1

No, non lo è. A questo punto, l'oggetto derivato è già costruito. –

risposta

10

seguire, passo dopo passo:

1) Si costruisce l'oggetto.

2) si esegue il seguente pezzo di codice:

if (started_.load() == false) { 
    started_ = true; 
    thread_ = std::thread([&]() { 
    run(); 
    }); 
} 

Il filo genitore ritorna immediatamente main() dove immediatamente uscite e distrugge l'oggetto.

Ecco il bug:

  • Non ha la garanzia che il filo è iniziato nel start() sta per raggiungere la chiamata a run(), al di sopra, prima che il thread genitore termina il processo. Sia il thread secondario, sia il thread principale vengono eseguiti contemporaneamente.

Quindi, ogni tanto, il thread padre distrugge l'oggetto prima che il thread secondario entri in marcia e chiama run().

A questo punto, l'oggetto il cui metodo run() viene richiamato, è già stato distrutto.

Comportamento non definito.

L'asserzione che stai colpendo, ogni tanto, è un possibile risultato di questo comportamento indefinito.

+1

Il 'thread_.join()' nel distruttore della classe base 'non dovrebbe garantire che l'oggetto non venga distrutto prima dell'esecuzione del thread? – subzero

+2

@subzero Qui ci sono tre passaggi: (A) Distruttore di classe derivato (B) Corpo di thread eseguito (C) Distruttore di classe base. (A) sicuramente accade prima (C). Ma nulla impedisce che (A) avvenga prima (B) prima (C), il che significa che il vtable viene rimosso prima che 'run()' sia chiamato prima che 'join()' sia chiamato. – Barry

+1

@subzero 'assicurati che l'oggetto non venga distrutto. Nella programmazione MT, è compito tuo avere il controllo sulla durata dell'oggetto. Lasciandolo fino al compilatore dà invariabilmente problemi come questo. – PaulMcKenzie

Problemi correlati