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

《C++ Concurrency in Action(第二版)》线程安全栈pop实现的异常安全性疑问

线程安全栈pop方法的异常安全性疑问解答

先回顾原实现代码:

template <typename T>
std::shared_ptr<T> threadsafe_stack<T>::pop()
{
  std::lock_guard<std::mutex> lock(m);

  // `data` 是类型为 `std::stack<T>` 的数据成员
  if(data.empty()) throw empty_stack();   // <--- 2
  std::shared_ptr<T> const res(
    std::make_shared<T>(std::move(data.top())));  // <--- 3
  data.pop();      // <--- 4
  return res;
}

书中给出的解释:

创建res(步骤3)可能会因两种原因抛出异常:一是std::make_shared无法为新对象及引用计数所需的内部数据分配内存;二是待返回数据项的拷贝构造函数或移动构造函数在将数据拷贝/移动到新分配的内存时抛出异常。这两种情况下,C++运行时和标准库都能确保无内存泄漏,且已创建的新对象(若有)会被正确销毁。由于此时尚未修改底层栈,因此是安全的。

针对疑问逐一解答:

1. 上述解释是否正确?

书中的解释部分正确,但存在隐含前提:

  • 对于std::make_shared分配内存失败的情况,解释完全正确——此时没有任何对象被创建,栈的状态也未被修改,无内存泄漏。
  • 对于拷贝构造函数抛异常的情况,解释也正确——拷贝操作不会修改原栈顶元素,栈状态保持完整。
  • 但对于移动构造函数抛异常的情况,解释的正确性依赖于T的移动构造函数抛出异常时,原栈顶元素的状态未被破坏。而C++标准仅规定:移动构造函数抛出异常时,原对象的状态是「未指定的(unspecified)」,除非类型T明确保证移动操作的强异常安全性。如果T的移动构造函数在抛异常前已经修改了原对象,那么栈的状态就会被破坏,此时书中的「尚未修改底层栈」的结论不成立。

2. 若数据项的移动构造函数抛出异常且导致数据项损坏,栈的状态是否已被破坏?

是的。如果移动构造函数在执行过程中抛出异常,且已经修改了原栈顶元素的状态(比如移动了部分成员,剩下的成员处于无效或不一致状态),那么栈顶元素就会处于损坏状态,整个栈的一致性被破坏。此时该实现不具备强异常安全性——强异常安全性要求异常发生后,程序状态完全回滚到异常发生前的状态,而这里栈的状态已经被修改,后续读取栈顶元素会得到损坏的数据。

3. 该实现的有效性条件是什么?

该实现要保证强异常安全性(即异常发生后栈状态不受影响),必须满足以下任一条件:

  • 类型T的移动构造函数不会抛出异常(即std::is_nothrow_move_constructible_v<T>为true);
  • 类型T的移动构造函数抛出异常时,原对象的状态仍保持有效且未被修改(即T明确保证移动操作的强异常安全性)。
    如果这两个条件都不满足,那么当移动构造函数抛异常时,栈顶元素可能处于损坏状态,后续对栈的操作会出现不可预期的问题。

内容的提问来源于stack exchange,提问作者for_stack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:46:08