假设下面的类声明:
class NTree
{
private:
const T* fKey;
NTree<T, N>* fNodes[N]; // N subtrees of degree N
NTree();
...
}
我们可以在其中添加一些 fNodes,代表给定索引的子树。这些将使用 new
动态分配。但是,有些元素是静态的,而不是动态分配的:
public:
static NTree<T, N> NIL; // sentinel
...
我们选择使用上面提供的默认构造函数在堆栈上分配它。
template<class T, int N>
NTree<T, N> NTree<T, N>::NIL;
现在,假设我们希望删除一个 NTree。 NTree 类是递归的,其中包含指向 NTree 的指针。 这就是我正在努力解决的问题。
我理解析构函数背后的逻辑,如果我们有例如
class MyClass
{
private:
TypeA * myA;
TypeB * myB;
TypeC * myC;
...
}
我们可以使用析构函数来防止这些指针悬空或丢失。
~MyClass()
{
delete myA;
delete myB;
delete myC;
}
但是,当涉及到递归类时,我不知道如何围绕这个思考,如何理解删除。
一个简单的想法:
template<class T, int N>
NTree<T, N>::~NTree()
{
delete[] fNodes;
}
但是,它不会工作,因为一些节点是 NIL(堆栈分配),所以删除它们会导致崩溃。
另一个想法是:
template<class T, int N>
NTree<T, N>::~NTree()
{
for (int i = 0; i < N; i++)
{
delete fNodes[i];
}
}
但是,这将导致堆栈溢出,因为每次递归调用 ~NTree()
以及以下内容:
template<class T, int N>
NTree<T, N>::~NTree()
{
for (int i = 0; i < N; i++)
{
if (fNodes[i] != &NIL)
delete fNodes[i];
}
}
导致读取异常,因为递归调用将为特定堆栈帧释放 fNodes[i],因此尝试访问该内存是无效的。
所以我的问题是,我怎样才能删除一个成员变量,其中该成员被递归定义为同一个类?
如何让我的析构函数工作?
编辑:尝试提供更多信息而不使其过于复杂
我正在定义一个析构函数,所以向您展示我的复制构造函数和赋值运算符可能是明智的:
template<class T, int N>
NTree<T, N> & NTree<T, N>::operator=(const NTree & aOtherNTree)
{
//This is an already initialized object.
if (this != &aOtherNTree)
{
fKey = aOtherNTree.fKey;
for (int i = 0; i < N; i++)
{
if (fNodes[i] == &NIL)
continue; //continue if nil
delete fNodes[i]; //important -- so no dangling pointer
fNodes[i] = new NTree<T, N>; //allocate memory
fNodes[i] = aOtherNTree.fNodes[i]; //assign
}
}
return *this;
}
..
template<class T, int N>
NTree<T, N>::NTree(const NTree & aOtherNTree)
{
//This is a new object, nothing is initalized yet.
fKey = aOtherNTree.fKey;
for (int i = 0; i < N; i++)
{
if (fNodes[i] == &NIL)
continue;
fNodes[i] = new NTree<T, N>;
fNodes[i] = aOtherNTree.fNodes[i];
}
}
我希望这显示了我在析构函数中分配需要显式删除的内存时的所有实例。 NIL 是一个哨兵,我们总是为 NIL 分配一个叶子。
这部分由教授提供,这是我们设置初始对象的地方:
NS3Tree root(A);
root.attachNTree(0, *(new NS3Tree(A1)));
root.attachNTree(1, *(new NS3Tree(A2)));
root.attachNTree(2, *(new NS3Tree(A3)));
root[0].attachNTree(0, *(new NS3Tree(AA1)));
root[1].attachNTree(0, *(new NS3Tree(AB1)));
root[1].attachNTree(1, *(new NS3Tree(AB2)));
A1、A2等都是字符串
最佳答案
你的复制构造函数和赋值运算符都是完全错误的。
if (fNodes[i] == &NIL)
continue; //continue if nil
delete fNodes[i]; //important -- so no dangling pointer
这是错误的逻辑。如果您的旧子值是 NIL,它将永远保持为 NIL,因为它永远不会被分配。这应该是:
if (fNodes[i] != &NIL)
delete fnodes[i];
当然在复制构造函数中,上面的片段不应该出现,因为 fNodes[i] 没有任何确定的值。它应该只出现在作业中。
现在
fNodes[i] = new NTree<T, N>; //allocate memory
fNodes[i] = aOtherNTree.fNodes[i]; //assign
你分配一些节点,然后立即用另一个指针覆盖指向它的指针,由另一个节点管理。因此,第一个分配没有任何影响,除了内存泄漏。第二个将在稍后导致错误。这是一个正确的调用
if (aOtherNTree.fNodes[i] == &NIL)
fNodes[i] = &NIL;
else
fNodes[i] = new NTree<T, N> (*aOtherNTree.fNodes[i]); // make a new copy
另一种 else 子句是
else {
fNodes[i] = new NTree<T, N>;
*fNodes[i] = *aOtherNTree.fNodes[i]); // assign the object, not the pointer
}
我建议编写一个调试函数来打印一棵树,包括每个节点的地址。调试时,打印您创建的每棵树以确保不会发生指针共享。
关于c++ - 具有指针 C 成员的类 C 的析构函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37382245/