返回shared_ptr和异常安全

时间:2016-04-14 06:58:01

标签: c++ c++11 stack shared-ptr move-semantics

我正在阅读“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行调用了复制构造函数。如果复制构造函数抛出异常,则堆栈尚未修改,我们就是好的。

但是我不知道它与这个片段的区别很大:

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()行进行了复制构造函数调用,在返回时有移动构造函数。同样,如果复制构造函数抛出异常,我们就很好,因为堆栈尚未修改。移动contstructor不应该抛出异常。

我错过了什么吗?将shared_ptr比较返回到(可移动)T会有什么好处?

2 个答案:

答案 0 :(得分:1)

如果T不可移动,您的代码最终可能会执行多个复制构造。在这种情况下,使用shared_ptr在堆上分配副本可能会更有效。

即使T可移动,您的代码仍可能执行多个(可能代价高昂的)移动构造。与移动或复制构造任何可能的类型shared_ptr相比,已知移动构造和/或复制构造T相对便宜。

当然,您的里程可能会因具体类型,编译器以及操作环境和硬件而异。

答案 1 :(得分:1)

move-constructor可能会抛出(一般情况下)。 std::shared_ptr<T>有一个noexcept移动构造函数。

因此,在第一种情况下,return res;无法抛出,但在第二种情况下,return res;可能会抛出(并且data.pop()已被调用)。

相关问题