Va bene avviare una discussione all'interno di un costruttore di una classe

5

Questa è una domanda puramente di filosofia del design nel contesto del C ++.

È una filosofia di design accettabile avviare un thread all'interno di un costruttore? Ho una libreria la cui unica responsabilità è gestire in modo asincrono alcune richieste dalle API esposte. Affinché funzioni, deve iniziare un thread internamente.

Ho MyClass che è una classe pubblica esposta dalla mia libreria.

//MyClass.h
MyClass {
private:
    std::thread m_member_thread;
}

Va bene iniziare il thread all'interno del costruttore in questo modo?

//MyClass.cpp
MyClass::MyClass() {
    // Initialize variables of the class and then start the thread
    m_member_thread = std::thread(&MyClass::ThreadFunction, this);
}

Oppure, è meglio esporre un metodo che il codice utente della mia libreria chiama esplicitamente per avviare il thread?

//MyClass.cpp
MyClass::MyClass() {

}

MyClass::StartThread() {
    m_member_thread = std::thread(&MyClass::ThreadFunction, this);
}

O davvero non importa?

PS:
Sì, il thread è leggero e non esegue operazioni molto pesanti. Voglio evitare le risposte che dicono, dipende da cosa fa il thread ecc. Questa è una domanda puramente di filosofia del design.

    
posta Game_Of_Threads 29.11.2018 - 10:35
fonte

3 risposte

10

Di solito l'inizializzazione dell'oggetto nel costruttore è preferibile in quanto riduce il numero di stati e rende più facile ragionare sull'oggetto. Nessun metodo di avvio e arresto a meno che il tuo design non abbia davvero bisogno di questo stato! Allo stesso modo, i metodi init che devono essere chiamati dopo il costruttore sono un enorme odore di design - se non è possibile inizializzare un oggetto non dovrebbe ancora esistere, che può essere modellato con un puntatore nullo. Anche le operazioni stateful possono rendere impossibile l'utilizzo di const in modo appropriato.

Il consiglio "I costruttori non dovrebbero fare il vero lavoro" non si applica molto bene al C ++. Il suo obiettivo è quello di eseguire un costruttore dovrebbe essere super economico, simile al concetto "banalmente costruibile" del C ++. Seguendo questo consiglio, qualsiasi risorsa dovrebbe essere acquisita prima della costruzione e passata come argomenti al costruttore, ad es. con una funzione membro della fabbrica statica pubblica che chiama un costruttore privato. Ma questo viola RAII (l'acquisizione delle risorse è di inizializzazione, non la precede) e rende difficile scrivere codice eccezionalmente sicuro. Questo non è un buon approccio in C ++.

Pertanto, è assolutamente corretto creare un thread all'interno di un costruttore (e generare un'eccezione se qualcosa va storto).

La libreria standard include un paio di esempi di comportamento correlato. Gli oggetti file come std::ifstream aprono correttamente / creano qualsiasi file all'interno del loro costruttore. Sfortunatamente il loro design è difettoso in quanto l'oggetto risultante potrebbe non essere in uno stato utilizzabile se si è verificato un errore. Un design migliore sarebbe stato quello di generare errori o utilizzare una funzione factory che restituisce un valore opzionale. In secondo luogo, considera i buffer come std::vector o std::string . Possono allocare memoria nel costruttore ma non devono. Invece, le allocazioni possono essere posticipate fino a quando i caratteri / elementi non vengono aggiunti al buffer e esauriscono la sua capacità. Nella progettazione, potrebbe essere preferibile posticipare l'inizializzazione della filettatura fino a quando non viene effettivamente utilizzata, ad es. fino a quando non è prevista una certa unità di lavoro per il thread.

    
risposta data 29.11.2018 - 11:57
fonte
4

Dipende dalla filosofia del design sul fatto che i costruttori debbano fare un lavoro reale. Se sono autorizzati a farlo, va bene, non importa quale sia il thread. In caso contrario, non lo è.

Pensa anche agli invarianti della tua classe. Il thread in esecuzione sullo sfondo è un invariante della tua classe? Gli invarianti devono essere stabiliti nel costruttore.

Infine, considera questo: il costruttore di std::thread inizia un thread. Questo può essere un suggerimento sulla filosofia di progettazione dei progettisti di librerie standard.

    
risposta data 29.11.2018 - 10:54
fonte
2

Per me la domanda è, in base alle tue esigenze, quanto è legata la durata della tua discussione alla vita dell'oggetto. Come punto di considerazione, considera un pool di thread.

class ThreadPool
{
    ...
private:
    MyClass threads[max_threads];
};

In questo caso potrebbe diventare un fardello se la durata dei tuoi oggetti è legata alla durata dei tuoi thread, perché il pool di thread potrebbe voler allocare / inizializzare N thread e oggetti corrispondenti in anticipo e avviare e fermare quelli fili indipendentemente da un ctor e da un dtor. Puoi iniziare a combattere contro il linguaggio, specialmente se le prestazioni diventano un problema, piuttosto che lavorare in armonia con il linguaggio se i tuoi bisogni richiedono effettivamente una separazione di queste due vite (durata del filo contro durata dell'oggetto) mentre stai ancora cercando di legare ostinatamente i due insieme.

Potresti voler rendere questi due indipendenti l'uno dall'altro, se vuoi riutilizzare una risorsa thread esistente e avviarla ancora una volta con un nuovo stato thread e una funzione per chiamare senza effettivamente creare un nuovo thread e creare un'istanza di un nuovo oggetto corrispondente.

In questo caso sopra, potresti volere un separato start_thread o start o run di metodo per iniziare a eseguire il thread indipendentemente da quando viene costruito il wrapper, nonché un metodo per terminare il thread prima della distruzione dell'oggetto (anche se probabilmente sarà comunque una buona idea assicurare la chiusura del thread nella distruzione dell'oggetto).

Quindi questa è la preoccupazione principale che prenderei in considerazione in questo caso particolare. Semplificherebbe sicuramente le cose se fosse possibile legare la durata di un oggetto alla durata del thread. Eppure potrebbe aprire più opzioni di design se non lo fai e separare i due. A volte c'è un merito nel separare " avviare un'operazione " da " costruire un oggetto " ... o meno e che potrebbe semplicemente tradurre in più codice di quello che dovrebbe essere idealmente richiesto se non hai mai veramente bisogno di questa distinzione.

    
risposta data 03.12.2018 - 08:10
fonte

Leggi altre domande sui tag