用于异常处理的基类技术

Irf*_*rfy 8 c++

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
}
Run Code Online (Sandbox Code Playgroud)

如果基本构造函数成功,即使子类的复制构造函数抛出,也会保证基本析构函数中的内存清理.

我的问题是:

  1. 除上述内容外,该方法还有其他任何好处吗?
  2. 当我自己解决问题时,我想出了这个拷贝构造函数:

    // 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_);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    我发现与这种方法相比,拥有一个基类有些麻烦.有没有理由将基类技术优先于这种临时复制然后交换方法?请注意,Dave和我已经拥有该swap()成员,因为我们在我们的使用中operator=().

  3. 戴夫亚伯拉罕的技术似乎并不为人所熟知(根据谷歌的说法).它有不同的名称,是标准做法,我错过了什么吗?

笔记:

  • 假设Dave Push()在一个循环中等同于我的用法std::copy
  • 让我们保持智能指针的答案,因为它们的使用会在本练习中明确地管理内存

Pyr*_*rce 1

从行为上来说,这两个实现是相同的。它们都设置了一个托管内存分配对象,如果构造函数失败,该对象将在范围退出时进行清理。复制到临时变量可能会更昂贵,但正如评论中所述,std::move可能会抵消此类额外成本。回答您的具体问题:

  1. Abraham 的示例确实使堆分配远离了实际的类实现细节。在您的代码中,如果您在复制数组之前/之后进行更复杂的内存操作,那么确保正确管理所有实体可能会稍微困难一些。否则,我看不到任何您尚未涵盖的关于第一个实现的行为的风格之外的明确细节。
  2. Abraham 的实现确实将清理工作抽象到了一个位置。如果多个类使用StackBase<T>,那么它们每个都可以安全地假设它们的动态内存将在抛出异常时被清理。在您的实现中,您需要重写(至少部分)临时对象并交换代码以实现相同的目的。实际上,此实现减少了实现 StackBase 的多个子类的行数。但是,如果您希望在其他基类之上进行额外的内存分配,那么您的实现将避免多重继承。您的代码还避免了模板代码膨胀编译时间/大小 - 尽管我通常不认为这在大多数情况下是一个很大的负面影响。我可能会使用与您的代码接近的代码作为默认值,除非我试图编写非常通用的用例代码。
  3. 我不知道这种方法是否有具体的名称——如果找到的话我会更新它——但我之前至少在一本 C++ 编程书中看到过它的使用。