假设以下类声明:
class NTree
{
private:
const T* fKey;
NTree<T, N>* fNodes[N]; // N subtrees of degree N
NTree();
...
}
我们可以在其中添加一些fNode,表示给定索引的子树。这些将使用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等是字符串
答案 0 :(得分:1)
您的复制构造函数和赋值运算符都是完全错误的。
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
}
我建议编写一个打印树的调试函数,包括每个节点的地址。在调试时,打印您生成的每个树以确保不会发生指针共享。