You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

《Effective Modern C++》中ThreadRAII默认移动赋值是否存在错误?

《Effective Modern C++》Item37中ThreadRAII实现的问题分析

书中的原始ThreadRAII实现

《Effective Modern C++》的Item37展示了ThreadRAII类的实现,其核心是在析构时对内部持有的std::thread执行join或detach操作,代码如下:

#include <thread>

class ThreadRAII
{
public:
    enum class DtorAction { join, detach };

    ThreadRAII(std::thread&& t, DtorAction a)
        : action(a)
        , t(std:: move(t))
    {}

    ~ThreadRAII()
    {
        if(t.joinable())
        {
            if(action == DtorAction::join)
                t.join();
            else
                t.detach();
        }
    }

    ThreadRAII(ThreadRAII&&) = default;

    ThreadRAII& operator=(ThreadRAII&&) = default;

    std::thread& get() { return t; }

private:
    DtorAction action;
    std::thread t;
};

int main()
{
    ThreadRAII t{std::thread{[]{}}, ThreadRAII::DtorAction::join};

    t = ThreadRAII{std::thread{[]{}}, ThreadRAII::DtorAction::detach};

    return 0;
}

作者提到,由于显式声明了析构函数,编译器不会自动生成移动操作,因此建议使用= default来实现移动构造函数和移动赋值运算符。

原始实现的问题

运行上述代码时会触发std::terminate终止程序,原因在于:

  • 默认移动构造函数是可行的:它会正确移动std::thread对象和action枚举值,原对象的std::thread会被置为不可joinable状态,析构时不会引发问题。
  • 默认移动赋值运算符存在缺陷:它会直接对std::thread执行移动赋值,而std::thread的移动赋值要求目标线程必须处于不可joinable状态——如果当前对象持有的线程还可joinable,移动赋值会直接调用std::terminate终止程序。原始实现的默认移动赋值未先处理当前对象的可joinable线程资源,导致程序崩溃。

修正后的ThreadRAII实现

针对该问题,修正后的实现需要在移动赋值时先释放当前对象的线程资源,再执行移动操作,代码如下:

#include <thread>

class ThreadRAII
{
public:
    enum class DtorAction { join, detach };

    ThreadRAII(std::thread&& t, DtorAction a)
        : action(a)
        , t(std:: move(t))
    {}

    ~ThreadRAII()
    {
        release();
    }

    ThreadRAII(ThreadRAII&&) = default;

    ThreadRAII& operator=(ThreadRAII&& rhs)
    {
        release();
        action = rhs.action; 
        t = std::move(rhs.t);
        return *this;
    }

    std::thread& get() { return t; }

    void release()
    {
        if(t.joinable())
        {
            if(action == DtorAction::join)
                t.join();
            else
                t.detach();
        }
    }

private:
    DtorAction action;
    std::thread t;
};

int main()
{
    ThreadRAII t{std::thread{[]{}}, ThreadRAII::DtorAction::join};

    t = ThreadRAII{std::thread{[]{}}, ThreadRAII::DtorAction::detach};

    return 0;
}

修正核心点:

  1. 提取release()方法统一处理线程的join/detach逻辑,析构函数复用该方法。
  2. 自定义移动赋值运算符,先调用release()释放当前对象的线程资源(确保std::thread处于不可joinable状态),再移动赋值action和std::thread对象。

结论

书中建议使用= default实现移动赋值运算符的做法确实存在错误,因为它没有考虑std::thread移动赋值的前置条件。用户在书籍勘误列表中未找到该问题,建议联系作者提交此勘误。


内容的提问来源于stack exchange,提问作者Hugo Costa Machado

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 03:11:50