2014-05-11 16 views
8

Vorrei utilizzare una classe che gestisce un thread (o più thread). Utilizzando la composizione, questo sarebbe simile:Esiste un modo sicuro per avere un file std :: thread come membro di una classe?

class MyClass{ 
private: 
    std::thread mythread; 
    void _ThreadMain(); 
public: 
    MyClass(); 
    // other fields 
} 

Poiché il costruttore predefinito per un std::thread è inutile, avrei bisogno di chiamare esplicitamente in MyClass costruttore:

MyClass::MyClass() : mythread(&MyClass::_ThreadMain,this) {} 

Tuttavia, in questo caso il _ThreadMain il metodo sarà probabilmente eseguito prima cheMyClass sia stato creato, causando qualsiasi tipo di comportamento strano. Questo è chiaramente pericoloso. Come posso risolvere questo?

Una soluzione ovvia sarebbe quella di utilizzare un puntatore a std::thread invece, e aggiungere un'altra funzione membro:

void MyClass::Start(){ 
    // This time mythread is of type std::thread* 
    mythread = new std::thread(&MyClass::_ThreadMain,this); // One could use std::unique_pointer instead. 
} 

che accendere quel filo. In questo caso verrebbe chiamato dopo che la classe è stata costruita, che sarà davvero sicura.

Tuttavia, mi chiedo se esiste una soluzione ragionevole che mi consenta di non utilizzare i puntatori per questo. Sembra che dovrebbe essere possibile in qualche modo (hey, ci deve essere un modo per lanciare un thread quando una classe è costruita!), Ma non riesco a trovare nulla che non possa causare problemi.

Ho considerato l'utilizzo di una variabile condizionale in modo che attenda fino a quando il costruttore non ha eseguito il suo lavoro, ma non posso utilizzarne uno prima che la classe sia costruita, giusto? (Questo sarebbe anche inutile se MyClass era una classe derivata)

+7

Ometti il ​​'new' a' mythread = new std :: thread (& MyClass :: _ ThreadMain, this); ',' std :: thread' è mobile! Considera 'std :: bind' per legare la tua funzione membro della classe. –

+1

Puoi lasciare che il thread aspetti (all'inizio) finché non viene detto di procedere. –

+1

Il problema qui è che se il metodo '_ThreadMain' è virtuale, il thread chiamerà la funzione sbagliata. Sarei molto interessato a vedere una soluzione a questo in cui il costruttore fa la cosa giusta. –

risposta

6

Non c'è modo migliore, in generale, di avere una funzione separata Start.

Supponiamo che MyClass sia una classe base per una futura classe (sconosciuta) Derived. Se il thread viene avviato mentre (o prima) viene eseguito il costruttore MyClass, rischia sempre di chiamare l'implementazione "errata" di alcune funzioni virtuali sovrascritte da Derived.

L'unico modo per evitare questo è quello di avere il filo aspettare fino a dopo Derived è completamente costruito, e l'unico modo per farlo è quello di chiamare qualche altra funzione dopo il costruttore Derived completa per raccontare il filo di "andare". .. Il che significa che devi avere una sorta di funzione Go invocata separatamente.

Si potrebbe anche solo avere una funzione Start invocata separatamente e rinunciare alla complessità dell'attesa.

[Update]

Si noti che, per le classi complesse, "Two-fase di costruzione" è un idioma consigliato da alcuni. L'avvio del thread si adatterebbe perfettamente nella fase di "inizializzazione".

4

È possibile utilizzare un filo in combinazione con semantica move:

class MyClass final 
{ 
private: 
    std::thread mythread; 
    void _ThreadMain(); 
public: 
    MyClass() 
     : mythread{} // default constructor 
    { 
     // move assignment 
     mythread = std::thread{&MyClass::_ThreadMain, this}; 
    } 
}; 

La mossa operatore di assegnazione è documentato nella pagina seguente. In particolare, è noexcept e non viene creato alcun nuovo thread.

+0

Quando si sposta un oggetto thread, inizia un nuovo thread di esecuzione o lo sposta effettivamente? – Ben

+3

Questa soluzione può incorrere in problemi, se c'è un puntatore vtable! Il puntatore vtable verrà inizializzato * dopo * il blocco del costruttore! – Klaus

+2

Ragazzi avete dei modelli per tutte quelle cose, vero? Ho appena considerato di postare il mio commento come risposta prima di vedere il tuo, ma ritrattato ... –

1

Considerare la separazione dell'attività dalla gestione e avvio del thread.

Una classe crea un corridore e tutte le primitive di sincronizzazione snd, come le altre maniglie che la lanciano. Ciò consente di fallire la costruzione del runnable prima dell'avvio del threading.

Significa anche che il runnable è completamente costruito prima di essere eseguito.

Ora un primo passaggio dovrebbe essere il std::thread, ma alcune cose che aiutano con l'interruzione e la pulizia e le continuazioni possono essere utili.

L'oggetto di esecuzione può essere un semplice chiamabile, oppure può aggiungere un supporto aggiuntivo per il runnable per interagire con esso.

Problemi correlati