GotW#8的任务是在 C++ 中实现一个异常中立的通用堆栈数据结构,假设只有模板参数的析构函数不抛出。诀窍是处理可能抛出的模板参数操作(构造函数、复制构造函数、赋值),以使堆栈在抛出时保持一致状态。
在解决方案中,Herb Sutter 说
为了使这个解决方案更简单,我决定不演示异常安全资源所有权的基类技术。
经过一番谷歌搜索,我找到了 Dave Abrahams 可以追溯到 1997 年的答案。在他的解决方案中,他处理了基类中内存的分配和删除,并在子类中实现了堆栈操作。这样,他确保了复制构造函数中的元素复制与内存分配是分开的,这样如果复制失败,无论如何都会调用基类析构函数。
作为参考,这是 Dave 的复制构造函数,并添加了我的评论:
// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
: StackBase<T>( rhs.Count() ) // constructor allocates enough space
// destructor calls delete[] appropriately
{
while ( Count() < rhs.Count() )
Push( rhs.v_[ Count() ] ); // may throw
}
如果基构造函数成功,则即使子类的复制构造函数抛出异常,也可以保证基析构函数中的内存清理。
我的问题是:
- 除了上述之外,该方法还有其他好处吗?
当我自己解决问题时,我想出了这个复制构造函数:
// v_ refers to the internal array storing the stack elements // vsize_ is the amount of space allocated in v_ // vused_ is the amount of space used so far in v_ Stack (const Stack &rhs) : vsize_ (0), vused_ (0), v_ (0) { Stack temp (rhs.vused_); // constructor calls `new T[num_elements]` // destructor calls `delete[] v_` std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw swap (temp); } void swap (Stack &rhs) { std::swap (v_, rhs.v_); std::swap (vused_, rhs.vused_); std::swap (vsize_, rhs.vsize_); }
与这种方法相比,我发现拥有一个基类有点麻烦。有什么理由应该首选基类技术而不是这种 temp-copy-then-swap 方法?请注意,我和 Dave 都已经拥有该
swap()
成员,因为我们在operator=()
.- Dave Abrahams 的技术似乎并不为人所知(根据 Google 的说法)。它有不同的名称吗?这是标准做法吗?我错过了什么吗?
笔记:
- 假设戴夫
Push()
在一个循环中等同于我的使用std::copy
- 让我们将智能指针排除在答案之外,因为它们的使用会带走本练习中显式管理内存的意义