返回 shared_ptr 和异常安全

Returning shared_ptr and exception safety

我正在阅读 "C++ Concurrency in action" 书并试图理解线程安全数据结构(例如堆栈)中的异常安全。作者指出,为了避免竞争条件,pop 应该执行这两个操作 - 从堆栈中弹出和 return 项目,但是:

If the pop() function was defined to return the value popped, as well as remove it from the stack, you have a potential problem: the value being popped is returned to the caller only after the stack has been modified, but the process of copying the data to return to the caller might throw an exception.

建议的解决方案如下:

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

我错过了什么吗? returning shared_ptr 与 returning(可移动)T 相比有什么好处?

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

即使 T 是可移动的,您的代码仍可能执行多个(可能 昂贵的)移动构造。与移动或复制构造任何可能的类型 T.

相比,已知移动构造 and/or 复制构造 shared_ptr 相对便宜

当然,您的里程可能会有所不同,具体取决于确切的类型、您的编译器以及您的操作环境和硬件。

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

所以在第一种情况下 return res; 不能抛出,但在第二种情况下 return res; 可能会抛出(并且 data.pop() 已经被调用)。