【问题标题】:Generic binary tree node destructor issue通用二叉树节点析构函数问题
【发布时间】:2012-03-16 23:23:40
【问题描述】:

我一直在处理一项任务,但现在我遇到了有问题的析构函数。我必须创建一个具有所有常用成员函数和一些特殊运算符的通用二叉树。还有一个限制:一切都必须迭代地工作,所以这次没有讨厌的递归黑客。

BinTreeNode 类的析构函数显然有问题,因为如果我像这样删除节点:

BinTreeNode<int> * node = new BinTreeNode<int>();
delete node; 

我仍然可以访问它的数据:

node->getData(); //should fail miserably

所以删除没有效果,但我不知道应该如何更正析构函数。 在我看来,算法应该是正确的,所以我怀疑我使用指针的方式有问题,但此时我很困惑,我什至不理解自己的代码。

目前为止的代码:

BinTree.h

#ifndef BINTREE_H_
#define BINTREE_H_

#ifndef NULL
#define NULL 0
#endif

#include "BinTreeNode.h"

template <class T>
class BinTree
{
    private:
        BinTreeNode<T> * root;
    public:
        //constructors and destructor
        BinTree():
            root(NULL){}

        BinTree(T data):
            root(new BinTreeNode<T>(data)){}

        ~BinTree();

        //search
        BinTreeNode<T> * search(T data);

        //insert
        bool insert(T data);

        //remove
        bool remove(T data);
};

template <class T>
BinTree<T>::~BinTree()
{
    delete root;
}

template <class T>
BinTreeNode<T> * BinTree<T>::search(T data)
{
    BinTreeNode<T> * node = new BinTreeNode<T>(data);
    BinTreeNode<T> * current = root;

    while (current != NULL)
    {
        if (*current == *node)
        {
            delete node;
            return root;
        }
        else if (*node < *current)
        {
            current = current->getLeft();
        }
        else
        {
            current = current->getRight();
        }
    }
    delete node;
    return NULL;
}

template <class T>
bool BinTree<T>::insert(T data)
{
    BinTreeNode<T> * node = new BinTreeNode<T>(data);
    BinTreeNode<T> * current = root;

    while (current != NULL)
    {
        if (*current == *node)
        {
            delete node;
            return false;
        }
        else if (*node < *current)
        {
            if (current->getLeft() == NULL)
            {
                current->setLeft(node);
                return true;
            }
            else
            {
                current = current->getLeft();
            }
        }
        else
        {
            if (current->getRight() == NULL)
            {
                current->setRight(node);
                return true;
            }
            else
            {
                current = current->getRight();
            }
        }
    }
    return false;
}

#endif

BinTreeNode.h

#ifndef BINTREENODE_H_
#define BINTREENODE_H_

#ifndef NULL
#define NULL 0
#endif

template <class T>
class BinTreeNode
{
    private:
        T data;
        BinTreeNode<T> *left, *right, *parent;
    public:
        //constructors and destructor
        BinTreeNode():
            data(NULL), left(NULL), right(NULL), parent(NULL){}

        BinTreeNode(T data):
            data(data), left(NULL), right(NULL), parent(NULL){}

        ~BinTreeNode();

        //set and get data member
        T getData() const;

        void setData(T data);

        //set and get left and right branches
        BinTreeNode<T> * getLeft() const;

        BinTreeNode<T> * getRight() const;

        void setLeft(BinTreeNode<T> * node);

        void setRight(BinTreeNode<T> * node);

        //set and get parent
        BinTreeNode<T> * getParent() const;

        void setParent(BinTreeNode<T> * node);

        //comparison operators
        bool operator<(const BinTreeNode<T>& node) const;
        bool operator>(const BinTreeNode<T>& node) const;
        bool operator==(const BinTreeNode<T>& node) const;
};

template <class T>
BinTreeNode<T>::~BinTreeNode()
{
    BinTreeNode<T> * current = this;
    BinTreeNode<T> * parent = NULL;
    while (current != NULL)
    {
        parent = current->getParent();
        if (current->getLeft() == NULL)
            current = current->getLeft();
        else if (current->getRight() == NULL)
            current = current->getRight();
        else
        {
            if (parent->getRight() == current)
                parent->setRight(NULL);
            else
                parent->setLeft(NULL);
             current = NULL; // this line (among others) is very suspicious
        }
        current = parent;
    }
}

template <class T>
T BinTreeNode<T>::getData() const
{
    return data;
}

template <class T>
void BinTreeNode<T>::setData(T data)
{
    this->data = data;
}

template <class T>
BinTreeNode<T> * BinTreeNode<T>::getLeft() const
{
    return left;
}

template <class T>
BinTreeNode<T> * BinTreeNode<T>::getRight() const
{
    return right;
}

template <class T>
void BinTreeNode<T>::setLeft(BinTreeNode<T> * node)
{
    node->setParent(this);
    left = node;
}

template <class T>
void BinTreeNode<T>::setRight(BinTreeNode<T> * node)
{
    node->setParent(this);
    right = node;
}

template <class T>
BinTreeNode<T> * BinTreeNode<T>::getParent() const
{
    return parent;
}

template <class T>
void BinTreeNode<T>::setParent(BinTreeNode<T> * node)
{
    parent = node;
}

template <class T>
bool BinTreeNode<T>::operator<(const BinTreeNode<T>& node) const
{
        return this->data < node.data;
}

template <class T>
bool BinTreeNode<T>::operator>(const BinTreeNode<T>& node) const
{
    return this->data > node.data;
}

template <class T>
bool BinTreeNode<T>::operator==(const BinTreeNode<T>& node) const
{
    return this->data == node.data;
}

#endif /* BINTREENODE_H_ */

【问题讨论】:

    标签: c++ data-structures binary-tree destructor


    【解决方案1】:

    您的 BinTreeNode 析构函数应该是:

    template <class T>
    BinTreeNode<T>::~BinTreeNode() {
        delete left;
        delete right;
    }
    

    这将递归调用左右的析构函数,释放分配给这些节点及其子节点的内存。因此,这将释放整个树。

    将NULL 分配给指针不会释放它指向的内存。

    另一方面,你提到的,删除后,这一行:

    node->getData();
    

    仍然返回数据,完全正常。删除会释放内存,但存储在其中的数据可能仍然可用一段时间,直到在该内存地址中写入新内容。访问已经释放的内存地址意味着未定义的行为。

    顺便说一句,您应该在 C++ 中使用“0”(不带引号)而不是 NULL。因此,没有必要使用#ifndef NULL(...)。

    编辑:我没有看到“没有递归”的评论。这是一个非递归算法:

    #include <deque>
    
    /* ... */
    
    template <class T>
    BinTreeNode<T>::~BinTreeNode() {
        std::deque deq;
        // we're going to delete our children
        deq.push_back(this);
        while(deq.size()) {
            BinTreeNode<T> *ptr = deq.front();
            deq.pop_front();
            if(ptr) {
                deq.push_back(ptr->left);
                deq.push_back(ptr->right);
                // we don't want the child nodes
                // to double delete the children
                ptr->left = 0;
                ptr->right = 0;
                // avoid deleteing ourselves
                if(ptr != this)
                    delete ptr;
            }
        }
    }
    

    我还没有测试过,但它应该可以工作。

    【讨论】:

    • 值得补充的是,虽然这很可能会访问曾经存储在现在未分配内存中的数据,但它在技术上是未定义的行为,因此它可以做任何事情。
    • @Charles Keepax 我添加了一行提到与访问免费地址相关的未定义行为。
    • 这种递归方法确实非常简洁,但正如我之前所说,我不允许在任何函数中使用递归。恕我直言,这是一个非常愚蠢的限制(考虑到递归对二叉树来说很容易),但我必须服从老师的意愿。
    • 顺便说一句,我不知道删除后可以访问数据(但现在看起来很明显)所以感谢您的澄清。使用 NULL 是 C 时代的一个坏习惯,所以是的,我真的应该放弃使用它。
    • 通常避免递归以避免填满主堆栈。在分配的内存中创建自己的类似堆栈的结构(例如在使用 std::deque 的示例中)非常适合此目的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-09
    • 2013-08-05
    • 1970-01-01
    • 2019-01-18
    • 2022-06-16
    • 2011-12-22
    • 1970-01-01
    相关资源
    最近更新 更多