C++ 当构造函数发生故障时,必须进行什么清理?

C++ 当构造函数发生故障时,必须进行什么清理?,c++,oop,constructor,C++,Oop,Constructor,我目前正在为搜索树等经典数据结构编写一个实现。我从B+树开始 所涉及的课程如下所示: 模板 类BPlusNode{ /* ... */ } 模板 类BPlusINode:公共BPlusINode{ /* ... */ } 模板 类BPlusLeaf:公共BPlusNode{ /* ... */ } 模板 类B簇树{ /* ... */ 私人: BPlusNode*根; /* ... */ } 我正在为我的树编写复制构造函数。这有点复杂,因为它需要在原始树上进行BFS搜索,以逐个复制每个节点(并

我目前正在为搜索树等经典数据结构编写一个实现。我从B+树开始

所涉及的课程如下所示:

模板
类BPlusNode{
/* ... */
}
模板
类BPlusINode:公共BPlusINode{
/* ... */
}
模板
类BPlusLeaf:公共BPlusNode{
/* ... */
}
模板
类B簇树{
/* ... */
私人:
BPlusNode*根;
/* ... */
}
我正在为我的树编写复制构造函数。这有点复杂,因为它需要在原始树上进行BFS搜索,以逐个复制每个节点(并相应地编辑子指针和父指针)。在复制过程中的某个时刻可能会发生内存分配失败(或任何其他不好的情况)。因此,我必须抛出一个异常来表示对象创建失败。但是我创建的所有节点会发生什么?它们会自动被销毁吗?还是我必须收拾残局

编辑:有关复制构造函数的一些精确性

模板
BPlusTree::BPlusTree(BPlusTree&tree){
std::列表至_cpy;
BPlusNode*n=nullptr,*p=nullptr,*cpy=nullptr;
将树向后推(tree.root);
而(!tocpy.empty()){
n=至_cpy.front();
n、 listChildren(to_cpy)//将@n的所有子项推到@to_cpy后面
//(按顺序)
到_cpy.pop_front();
cpy=n.clone();//可能会失败。
/*
*跟踪谁是@cpy的父节点的一些机制
*并在复制完所有指针后编辑子指针
*/
}
}

附加问题:我把根作为指针,因为当树进化时,因为B+树不是从上到下而是从下到上生长,根可以改变。这是正确的解决方案吗?(我指的是最具C++风格的)

如果构造函数抛出析构函数,则不会调用析构函数

因此,此时创建的任何依赖析构函数清理的内容都必须由您清理

异常之前在初始化器列表中构造的对象成员将使用其析构函数进行清理。因此,例如,如果您的类包含一些智能指针,则将进行清理

你的问题是“子节点会被破坏吗”?如果它们存储在智能指针或类似对象中。节点可以有一个弱链接返回到其父节点

如果您不能在类中存储智能指针,并且依赖析构函数来删除,那么在构造函数中使用智能指针,直到您知道它不再抛出为止。如果可用,您可以使用
std::unique\u ptr
,如果您只有这些,则可以使用
std::auto\u ptr
。无论哪种方式,都可以使用
release
将其分配给类成员指针。如果您需要将它们存储在一个向量中,那么
unique\u ptr
非常有用,因为您可以将它们存储在一个(临时)向量中,然后在最后运行并调用
release

否则,如果您不想要隐式的两阶段构造,请在幕后进行

class ChildNodeManager
{
    friend class Node; // this implements detail of Node

    ChildNodeManager() {} // constructor that never throws, might initialise 
       // some pointers to nullptr.

    void addNode( Node * node ); // might throw but will leave in a stable state
         // if it does, i.e. how it was before you tried addNode. Destructor 
         // will work safely

     ~ChildNodeManager() { // do cleanup of added nodes }

    // probably disable copy construction and assignment
};

class Node
{
   ChildNodeManager myChildren;

   public:
      Node( ... ) // might throw

};

< P>作为Node构造函数的主体中的一个完全构建的成员,即使节点的构造函数在中间某个地方失败,它也将被正确地销毁。所有已经添加到其中的节点都将被清除。

如果构造函数失败,所有的析构函数都将被完全清除 调用构造的子对象,但不调用的析构函数 构造函数失败的对象

处理此问题的经典方法(所有 例如,我看到的
std::vector的实现)
将内存管理放在一个私有基类中
比如:

因为这个基类将在您进入 进入树的实际构造函数代码,它的析构函数 会被打电话的

只要 通过
根目录访问的结构保持足够的一致性
允许适当的清理。甚至这种限制也可以放松
对于短时间间隔,您可以确保不会出现异常
被抚养。(例如,在重新平衡树的同时
操作只涉及指针操作,而指针操作永远不会

提出一个例外。)

智能指针是一个显而易见的解决方案,至少在他不需要指向父级的反向指针的情况下是如此(否则,使某些指针变强而某些指针变弱会带来许多额外的、不必要的复杂性)。这不一定是一个好的解决方案,因为它将您锁定在一个特定的内存管理方案中;对于树结构来说,这通常不是最好的。例如,在标准容器的实现中通常不使用智能指针是有原因的。正如您所说的@JamesKanze,我读过关于智能指针的文章,我认为在重新平衡树时必须进行的所有指针编辑、插入/删除和所有可能出现的编辑都会非常复杂(父指针和子指针)。我编辑了这个问题,以便对当前的复制构造函数略作了解(目前没有清理操作)。我已经扩展了我的解决方案,以展示如何使用单独的类来管理子节点,并且对于该类,您使用两阶段构造,即首先构造,然后添加节点。由于它是完全构造的,它肯定会销毁。@Rerito如果您不需要指向父节点的指针,请使用智能指针(
std::shared_ptr
)并没有那么困难。但是,它相对效率低,并且限制了未来的发展(例如,添加指向父对象的指针变得更困难,并且几乎不可能从树更改为任意图).这里让我不安的是,复制树和删除树的过程处于同一抽象级别。这意味着我的基类将能够1.复制,2.删除
class TreeBase
{
    Note* root;
    friend class Tree;
    TreeBase() : root( nullptr ) {}
    ~TreeBase() { delete root; }  //  Or whatever is needed for cleanup.
};