admin管理员组

文章数量:1277608

I have a base using std::thread _threadCall in its destructor, but I need to initialize it from the subclass (imagine I have several subclasses, all initializing differently _threadCall).

class QueryBase
{
    std::condition_variable _cv;
    bool _run{ true };
    std::thread _threadCall;
    virtual ~QueryBase();
    void virtual CallRunner()=0;
}

QueryBase::~QueryBase()
{
    _run = false;
    _cv.notify_one();
    _threadCall.join();
}

subclass:

class __declspec(dllexport) AsyncQuery: public QueryBase
{
protected:
    MessageChangedCallback _log{};
public:
    AsyncQuery(MessageChangedCallback fn);
    void Add(const std::string& msg);
    void CallRunner() override;
}

AsyncQuery::AsyncQuery(MessageChangedCallback fn)
    : QueryBase(), _log(fn),
      _threadCall([&] { CallRunner(); }) //E0292 _threadCall is a non-static member
                                         // or base class of class AsyncQuery
{
}

Can I initialize it from the subclass constructor or in some way ? if not I have the obligation to write the same dtor in every single sublass ?

As alternative, I thought about declaring a delegate in the base-class, and I "define" it in the subclasses:

class QueryBase
{
    std::function<void()> f;
    void virtual CallRunner()=0;

QueryBase::QueryBase() : _threadCall(f)
{
}

AsyncQuery::AsyncQuery(MessageChangedCallback fn)
    : _log(fn)
{
    f = [&] {CallRunner();};
}

Where CallRunner() is defined (overrides a pure virtual) only in subclasses. Any pros/cons about this ?

I have a base using std::thread _threadCall in its destructor, but I need to initialize it from the subclass (imagine I have several subclasses, all initializing differently _threadCall).

class QueryBase
{
    std::condition_variable _cv;
    bool _run{ true };
    std::thread _threadCall;
    virtual ~QueryBase();
    void virtual CallRunner()=0;
}

QueryBase::~QueryBase()
{
    _run = false;
    _cv.notify_one();
    _threadCall.join();
}

subclass:

class __declspec(dllexport) AsyncQuery: public QueryBase
{
protected:
    MessageChangedCallback _log{};
public:
    AsyncQuery(MessageChangedCallback fn);
    void Add(const std::string& msg);
    void CallRunner() override;
}

AsyncQuery::AsyncQuery(MessageChangedCallback fn)
    : QueryBase(), _log(fn),
      _threadCall([&] { CallRunner(); }) //E0292 _threadCall is a non-static member
                                         // or base class of class AsyncQuery
{
}

Can I initialize it from the subclass constructor or in some way ? if not I have the obligation to write the same dtor in every single sublass ?

As alternative, I thought about declaring a delegate in the base-class, and I "define" it in the subclasses:

class QueryBase
{
    std::function<void()> f;
    void virtual CallRunner()=0;

QueryBase::QueryBase() : _threadCall(f)
{
}

AsyncQuery::AsyncQuery(MessageChangedCallback fn)
    : _log(fn)
{
    f = [&] {CallRunner();};
}

Where CallRunner() is defined (overrides a pure virtual) only in subclasses. Any pros/cons about this ?

Share Improve this question asked Feb 25 at 0:09 SoleilSoleil 7,4576 gold badges45 silver badges71 bronze badges 13
  • 4 Rather than initializing it, you can assign to it: in the body of AsyncQuery construtor, _threadCall = std::thread([&] { CallRunner(); }); – Igor Tandetnik Commented Feb 25 at 0:20
  • 2 Note that, if you ever add a class derived from AsyncQuery, you are going to have a data race on vtable pointer. Calling virtual functions from construtors is risky business. – Igor Tandetnik Commented Feb 25 at 0:28
  • It really sucks when the thread can run before the class that wraps it hasn't finished initializing all of the member variables. Even if the thread is the last member, what if the program trips over some hidden implementation detail. For example what if the thread function invokes a virtual function and the vtable hasn't been set up yet? – user4581301 Commented Feb 25 at 0:29
  • 1 Your second approach won't work - it passes f to the thread before f is assigned to. – Igor Tandetnik Commented Feb 25 at 0:29
  • @user4581301 even if it is a pure virtual in the base class ? (I want to force its definition in subclasses (ideally like an abstract method in c#)) – Soleil Commented Feb 25 at 0:36
 |  Show 8 more comments

2 Answers 2

Reset to default 1

It is usually bad idea to use inheritance in this way, maybe you should use std::jthread, but design discussions aside...

Way to do delayed construction is to use std::optional member.

struct B {
  std::optional<std::thread> t;
  ~B() {
    assert(t.has_value());
    std::cout << "joining" << std::endl;
    t->join();
    std::cout << "joined" << std::endl;
  }
};

struct D : public B {
  D() {
    t.emplace([] { std::cout << "hello from derived" << std::endl; });
  }
};

int main() { D d; }

godbolt

Just use C++20 std::jthread with a std::stop_token:

std::jthread my_async_q{ 
     [](std::stop_token finish){
        while(not finish.stop_requested())
        {/*repeat the job*/};
     };//lambda
};//my_async_q

All your dll or any other extension code needs provide is a function foo or a function object that satisfies std::invocable<decltype (foo),std::stop_token>. Then you can restart a thread like:

//if (my_async_q.joinable())
my_async_q.request_stop();

//Join before renewal:
my_async_q = std::jthread{foo};

The join member is automatically invoked upon destruction. This single standard class encapsulate all the functionality you are trying to achieve, without needing dynamic polymorphism.

本文标签: