GotW#8的任务是在C ++中实现异常中立的通用堆栈数据结构,假设只有模板参数的析构函数不抛出。诀窍是处理潜在的抛出模板参数操作(构造函数,复制构造函数,赋值),以便在堆栈抛出时使堆栈保持一致状态。
在解决方案中,Herb Sutter说
为了使这个解决方案更简单,我决定不为异常安全的资源所有权演示基类技术。
经过一些谷歌搜索后,我发现Dave Abrahams的this answer可追溯到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_);
}
与这种方法相比,我发现拥有基类有些麻烦。有没有理由认为基类技术比这种临时复制然后交换方法更受欢迎?请注意,Dave和我已经拥有swap()
成员,因为我们在operator=()
中使用了它。
注意:
Push()
在一个循环中等同于我对std::copy
的使用答案 0 :(得分:1)
行为上两个实现是相同的。它们都设置了一个托管内存分配对象,如果构造函数失败,它将在范围退出时进行清理。复制到临时变量可能会更昂贵,但正如评论中所述,std::move
可能会使这些额外成本无效。在回答您的具体问题时:
StackBase<T>
,则每个类都可以安全地假设如果它们抛出异常,它们的动态内存将被清除。在您的实现中,您需要重写(至少部分)临时对象和交换代码以实现相同的目的。实际上,这种实现减少了实现StackBase的多个子类的行数。但是,如果您希望在其他基类之上分配额外的内存,则实现会避免多重继承。您的代码也避免了模板代码膨胀编译时间/大小 - 尽管我通常不认为这在大多数情况下是一个大的负面,无论如何。除非我试图编写非常通用的用例代码,否则我可能会使用接近代码的东西作为默认值。