链表,树等的递归析构函数是不是很糟糕?

时间:2011-08-06 06:57:03

标签: c++ coding-style recursion destructor

对于我目前的学习练习,我正在研究链表和树木。我最近看到一个建议是通过让每个节点删除其子/子来递归地销毁数据结构。然而,在我发现的几乎所有示例中,节点析构函数都是空的,而某些管理类使用某种形式的迭代和删除来处理破坏。从可靠性和/或风格的角度来看,递归析构函数有什么本质上的坏处吗?

以下是我对这两种方法的理解的实施。

递归破坏:

#include <iostream>
struct Node {
  static int count;
  Node() : num_(count++), p_next_(0) {}
  ~Node() { 
    std::cout << "entering " << num_ << "\n";
    delete p_next_;
    std::cout << " leaving " << num_ << "\n";
  }
  const int num_;
  Node* p_next_;
};
int Node::count = 0;
int main () {
  Node* p_head = new Node();
  p_head->p_next_ = new Node();
  p_head->p_next_->p_next_ = new Node();
  delete p_head;
  return 0;
}

这是我对处理破坏的管理类的看法。假设我为Node定义了以下DTOR:

~Node() {std::cout << "Someone deleted " << num_ << "\n";}

我将定义以下LinkedList管理类和后续主

/* other stuff from above */
class LinkedList {
public:
  LinkedList() : p_head_(new Node()) {
    p_head_->p_next_ = new Node();
    p_head_->p_next_->p_next_ = new Node();
  }
  ~LinkedList() {
    while(Node* p_prev = p_head_) {
      p_head_ = p_head_->p_next_;
      delete p_prev;
    }
  }
private:
  Node* p_head_;
};
int main () {
  LinkedList* p_list = new LinkedList();
  delete p_list;
  return 0;
}

假设我正确地读取了我的结果,我的两个实现做了同样的事情。

关于我的递归破坏示例,我想我几乎总是需要某种管理类,当我用代码解决一个真正的问题时,它会持有头部的副本,但是管理类只需要删除头部/根节点以确保破坏整个数据结构。对我来说感觉更优雅,但我已经陷入了我认为整洁的代码的麻烦。

管理类是否应该负责确保所有内容都被正确删除?或者,底层数据结构更好地了解如何清理自己?是否有任何不明显的陷阱?

谢谢!

- 约旦

编辑:我想到了一个念头。如果我有一个非常长的节点链,我是否必须担心在第一个例子中的破坏期间堆栈溢出,因为递归正在发挥作用?

edit2:我想应该是显而易见的。而现在我觉得有点傻。在我的机器上,如果我有超过64910个节点,我就崩溃了。所以递归显然是一个问题。

2 个答案:

答案 0 :(得分:6)

如果对链接列表执行此操作,则会出现堆栈内存消耗问题。销毁这样的链表将导致Node的递归,而递归深度将随着链表的大小线性增长。

只需进行实验:在列表中输入数百万个节点,然后将其销毁。我打赌你会得到堆栈溢出(除非你配置你的线程来保留巨大的堆栈大小)。特别是在调试版本中,你很早就会用完堆栈。

OTOH为树木做这件事是可以的,至少从技术角度来看。树清理通常以递归方式实现,如果上述递归函数属于树或Node,则事实并非如此。

破坏树的递归深度将与树深度呈对数增长,这是可以的。

答案 1 :(得分:4)

考虑链接项目的所有权。

Node对象拥有它的下一个关键是否有意义? 一个节点拥有附加的卫星数据肯定是有道理的,所以你应该在节点被销毁时清除它。

LinkedList拥有自己的节点,所以它应该负责在没有任何内存剩余的情况下正确销毁它们。

LinkedList对象应该能够添加和删除节点,因此从所有权的角度来看,它负责清理它们,例如通过删除列表中的每个节点 反复。