【发布时间】:2016-08-05 13:53:42
【问题描述】:
我正在阅读“C++ Concurrency in action”一书并试图理解线程安全数据结构(例如堆栈)中的异常安全性。作者指出,为了避免竞争条件,pop 应该同时执行这两项操作 - 从堆栈中弹出和返回项目,但是:
如果 pop() 函数被定义为返回弹出的值,并将其从堆栈中移除,那么您就有一个潜在的问题:只有在堆栈被修改后,被弹出的值才会返回给调用者,但是复制数据以返回给调用者的过程可能会引发异常。
这里是建议的解决方案:
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw runtime_error("empty");
std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
data.pop();
return res;
}
在这种情况下,我们在make_shared 行调用了复制构造函数。如果复制构造函数抛出异常,堆栈还没有被修改,我们很好。
但是我看不出它与这个 sn-p 有什么不同:
T pop2() {
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw runtime_error("empty");
auto res = data.top();
data.pop();
return res;
}
这里我们在data.top() 行调用复制构造函数,返回时移动构造函数。同样,如果复制构造函数抛出异常,我们很好,因为堆栈尚未修改。移动构造函数不应该抛出异常。
我错过了什么吗?与返回(可移动)T 相比,返回 shared_ptr 有什么好处?
【问题讨论】:
-
移动构造函数允许抛出。
-
我这本书的 PDF 副本有
throw empty_stack()而不是throw runtime_error("empty")。书中的empty_stack类直接继承自std::exception。如果您问题中的runtime_error是std::runtime_error,请注意throw runtime_error("empty");might throw other exceptions as well。 -
是的,我的也有
empty_stack。我只是在示例中将其替换为runtime_exception。 -
请注意,
res是const会阻止移动操作并将强制复制(除非发生复制省略,这不能保证)。
标签: c++ c++11 stack shared-ptr move-semantics