能否在子类构造函数初始化基类声明的std::thread?如何实现?
C++基类线程成员在子类初始化的问题
我有一个基类QueryBase,其析构函数中使用成员变量std::thread _threadCall,但需要在子类中对该线程初始化(存在多个子类,每个子类的_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(); }
子类代码
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 { }
问题1:能否在子类构造函数中初始化该线程?若不能,是否必须在每个子类中编写相同的析构函数?
不能直接在子类构造函数的初始化列表中初始化基类的_threadCall——因为_threadCall是基类的私有成员(默认访问权限),子类无法直接访问,这也是编译器报E0292错误的原因。
不需要每个子类都编写相同的析构函数。你的基类已经声明了虚析构函数virtual ~QueryBase(),且其中已经实现了线程停止、通知、join的完整逻辑。子类对象销毁时会自动触发基类析构函数的调用,因此子类无需重复编写析构代码。
问题2:替代方案的优缺点分析
你提出的替代方案补全后代码如下:
class QueryBase { std::condition_variable _cv; bool _run{ true }; std::function<void()> f; std::thread _threadCall; virtual void CallRunner()=0; public: QueryBase() : _threadCall(f) { } virtual ~QueryBase() { _run = false; _cv.notify_one(); _threadCall.join(); } }; AsyncQuery::AsyncQuery(MessageChangedCallback fn) : QueryBase(), _log(fn) { f = [&] {CallRunner();}; }
优点
- 统一了基类的线程初始化逻辑,子类只需关注委托函数
f的赋值,避免了每个子类重复处理线程创建细节。 - 基类依然负责线程销毁逻辑,子类无需额外编写析构代码,保持了代码复用性。
缺点
- 未定义行为风险:基类构造函数启动线程时,子类构造还未完成,此时
f尚未被赋值,线程会调用空的std::function;同时,基类构造阶段调用纯虚函数CallRunner()会直接触发未定义行为(此时虚函数表指向基类,纯虚函数无实现)。 - 悬空引用问题:lambda使用
[&]捕获当前对象引用,若对象生命周期结束或线程执行时对象已销毁,会导致引用悬空,触发不可预料的错误。 - 性能开销:
std::function存在类型擦除的额外开销,虽然多数场景可忽略,但在性能敏感场景下需谨慎使用。
内容的提问来源于stack exchange,提问作者Soleil
相关产品推荐
相关产品推荐

