Search code examples
c++c++11raiistdthread

Inheritance, background thread and RAII


I have a base class that can start background thread, and stop it when needed. That thread calls two virtual methods Open() and Close(). So all inherited classes can re-implement this methods, but not starting/stoping thread routine (it more difficult than in example). I want to follow RAII principle and start/stop thid thread in constructor/destructor of base class.

The problem is, that calling virtual methods in constructor/destructor is a bad practice and didn't work in my case. Here is a shot example of my problem:

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

The output is:

Inherited open
Base close

And without sleep is:

Base open
Base close

My current approach is to call Start() method after constructor and Stop() before destructor, but I want solution with 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();
  }
}

Solution

  • The problem is independent of threads. If you call virtual methods in the constructor of the Base, the Inherited object is not yet created so the Base implementations of the methods are called (or you get an error if they are pure virtual). If you call virtual methods in the destructor of Base the Inherited object is already destroyed so again the Base version of the virtual methods are called again.

    Calling the methods from another thread does not change this behaviour. But the starting of the thread might take longer than the construction of the Inherited object so the object is fully constructed and the Inherited methods are called at the beginning of the worker thread.

    One solution is to move the RAII to another object. So you don't call Start and Stop in Bases constructor and destructor. Then you can build a StartStopThing which takes a Base (by reference or by pointer) and calls Start and Stop in its constructor and destructor. Or you build a StartStopThing template class which takes Inherited as template argument, builds an Inherited object and calls the Start and Stop methods.