2017-07-18 2 views
0

J'ai une classe de base qui peut démarrer le fil d'arrière-plan et l'arrêter si nécessaire. Ce thread appelle deux méthodes virtuelles Open() et Close(). Ainsi, toutes les classes héritées peuvent ré-implémenter ces méthodes, mais pas démarrer/arrêter la routine des threads (c'est plus difficile que par exemple). Je veux suivre le principe RAII et démarrer/arrêter ce thread dans le constructeur/destructeur de la classe de base.Héritage, fil d'arrière-plan et RAII

Le problème est que l'appel de méthodes virtuelles dans le constructeur/destructeur est une mauvaise pratique et n'a pas fonctionné dans mon cas. Voici un exemple tiré de mon problème:

#include <iostream> 
#include <thread> 
#include <atomic> 

class Base { 
public: 
    Base() { 
    bg_thread_ = std::thread([this] { 
     Open(); 
     while(!is_stop_) { 
     // do stuff 
     } 
     Close(); 
    }); 
    } 
    ~Base() { 
    is_stop_ = true; 
    if(bg_thread_.joinable()) { 
     bg_thread_.join(); 
    } 
    } 
private: 
    virtual void Open() { 
    std::cout << "Base open" << std::endl; 
    } 
    virtual void Close() { 
    std::cout << "Base close" << std::endl; 
    } 
    std::thread bg_thread_; 
    std::atomic<bool> is_stop_{false}; 
}; 

class Inherited : public Base { 
    virtual void Open() override { 
    std::cout << "Inherited open" << std::endl; 
} 
    virtual void Close() override { 
    std::cout << "Inherited close" << std::endl; 
} 
}; 

int main() { 
    Inherited inherited; 
    std::this_thread::sleep_for(std::chrono::seconds(1)); 
    return 0; 
} 

La sortie est:

Inherited open 
Base close 

Et sans sommeil est:

Base open 
Base close 

Mon approche actuelle consiste à appeler la méthode Start() après constructeur et Stop() avant destructeur, mais je veux une solution avec RAII.

void Start() { 
    bg_thread_ = std::thread([this] { 
    Open(); 
    while(!is_stop_) { 
    // do stuff 
    } 
    Close(); 
    }); 
} 

void Stop() { 
    is_stop_ = true; 
    if(bg_thread_.joinable()) { 
    bg_thread_.join(); 
    } 
} 
+0

Vous pouvez appeler 'Stop()' dans le destructeur et vous devriez être en sécurité. – rozina

+0

Si j'appelle 'Start()' dans le constructeur et 'Stop()' dans destructor, la sortie sera: Inherited open Base close –

+0

Actuellement, la classe de base 'Close' et' Open' sont purement virtuelles. Et si j'appelle 'Stop()' dans la classe Base destructor, le programme va planter avec l'erreur 'Pure virtual called called' –

Répondre

1

Le problème est indépendant des threads. Si vous appelez des méthodes virtuelles dans le constructeur du Base, l'objet Inherited n'est pas encore créé, donc les implémentations Base des méthodes sont appelées (ou vous obtenez une erreur si elles sont purement virtuelles). Si vous appelez des méthodes virtuelles dans le destructeur de Base, l'objet Inherited est déjà détruit et la version Base des méthodes virtuelles est à nouveau appelée. L'appel des méthodes à partir d'un autre thread ne modifie pas ce comportement.

Mais le démarrage du thread peut prendre plus de temps que la construction de l'objet Inherited afin que l'objet soit entièrement construit et les méthodes Inherited sont appelées au début du thread de travail.

Une solution consiste à déplacer le RAII vers un autre objet. Donc, vous n'appelez pas Start et Stop dans Base s constructeur et destructeur. Ensuite, vous pouvez construire un StartStopThing qui prend un Base (par référence ou par pointeur) et appelle Start et Stop dans son constructeur et destructeur. Ou vous construisez une classe de modèle StartStopThing qui prend Inherited comme argument de modèle, construit un objet Inherited et appelle les méthodes Start et Stop.