【问题标题】:Destructor of a class C with members of pointer C具有指针 C 成员的类 C 的析构函数
【发布时间】:2016-09-19 19:37:37
【问题描述】:

假设以下类声明:

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等都是字符串

【问题讨论】:

  • delete[] fNodes fNodes 没有被分配到new,所以你没有业务到delete 它。您可能想要 delete 数组的单个元素,假设 那些 已通过 new 获得。
  • @IgorTandetnik 啊,是的。你说得对。在这种情况下,删除 fNodes 肯定是行不通的。但是,我展示了两种尝试删除 fNodes 元素的方法,但没有成功。
  • 深度优先遍历树(使用显式堆栈,而不是递归调用)。自下而上删除节点。
  • @IgorTandetnik 好的,我会试试的。但是我预计的问题是在析构函数中使用delete 会自动强制递归?
  • 如果您删除的节点是叶节点并且没有任何自己的子节点,则不会。

标签: c++ pointers recursion tree


【解决方案1】:

您的复制构造函数和赋值运算符都完全错误。

        if (fNodes[i] == &NIL)
            continue; //continue if nil
        delete fNodes[i]; //important -- so no dangling pointer

这是错误的逻辑。如果你的 old child value 是 NIL,它将永远保持 NIL,因为它永远不会被分配。这应该是:

        if (fNodes[i] != &NIL)
            delete fnodes[i];

当然,上面的片段不应该出现在复制ctor中,因为 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
        }

我建议编写一个调试函数来打印一棵树,包括每个节点的地址。调试时,打印您制作的每棵树,以确保不会发生指针共享。

【讨论】:

  • 解决了,谢谢。会接受。我想从托管语言到非托管语言有其困难。无论如何,您关于“这是错误的逻辑”的第一个陈述,我看不出两者有何不同,请查看 bit.ly/1OSwg4E - 首先,我的代码,我检查节点是否等于 nil , 如果是,那么继续,在 continue 之后的任何语句都不会被执行,因此检查 nil 和 continue 之间没有功能上的区别,然后当它不为真时,我们分配,而不是检查 not nil,当为真时,赋值。你能解释一下吗?
  • 我知道我显然错了,但我想知道我为什么错了。
  • @Kevin 这不是同一个代码。在 continue 之后,您的真实代码在循环体中有语句。这就是区别所在。继续将导致它们被跳过。你永远不想跳过它们。
猜你喜欢
  • 2011-06-12
  • 2014-12-15
  • 2012-04-29
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 2020-11-08
  • 2012-02-29
  • 2010-11-23
相关资源
最近更新 更多