《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
相关产品推荐
相关产品推荐

