书中的第 37 项说明了 ThreadRAII 的实现,它可以加入线程或在线程被销毁时将其分离。通过声明析构函数,编译器不会生成移动操作,但在书中,作者说没有理由不能移动它们,并说编译器会生成正确的移动操作,并建议我们使用 '= default ' 实现。
#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;
}
但在上面的例子中,调用了 std::terminate。
我认为默认的移动构造函数应该没问题,但移动赋值不行,因为移动赋值必须在获取新资源之前释放当前资源。否则,赋值将破坏可连接的线程,从而导致程序终止。
我没有在本书的勘误表中看到这个问题。这本书说默认的移动赋值运算符应该没问题真的错了吗?我想确定并让其他人查看它以便与作者联系。
我认为应该是这样的:
#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;
}
最佳答案
是的,这是一个明确的 3(5) 违规规则。
一旦您有了自定义的 dtor,您就必须同样自定义您的特殊赋值和构造函数。在这里,销毁的线程导致连接或分离而分配给不连接的事实相对不一致。
作者大概想过assigning-to-empty的情况,算出来还好,没想到assigning-to-engaged。
关于c++ - Effective Modern C++ 书中 ThreadRAII 的实现是否正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74084172/