对于我目前的学习练习,我正在研究链表和树木。我最近看到一个建议是通过让每个节点删除其子/子来递归地销毁数据结构。然而,在我发现的几乎所有示例中,节点析构函数都是空的,而某些管理类使用某种形式的迭代和删除来处理破坏。从可靠性和/或风格的角度来看,递归析构函数有什么本质上的坏处吗?
以下是我对这两种方法的理解的实施。
递归破坏:
#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个节点,我就崩溃了。所以递归显然是一个问题。
答案 0 :(得分:6)
如果对链接列表执行此操作,则会出现堆栈内存消耗问题。销毁这样的链表将导致Node
的递归,而递归深度将随着链表的大小线性增长。
只需进行实验:在列表中输入数百万个节点,然后将其销毁。我打赌你会得到堆栈溢出(除非你配置你的线程来保留巨大的堆栈大小)。特别是在调试版本中,你很早就会用完堆栈。
OTOH为树木做这件事是可以的,至少从技术角度来看。树清理通常以递归方式实现,如果上述递归函数属于树或Node
,则事实并非如此。
破坏树的递归深度将与树深度呈对数增长,这是可以的。
答案 1 :(得分:4)
考虑链接项目的所有权。
Node对象拥有它的下一个关键是否有意义? 一个节点拥有附加的卫星数据肯定是有道理的,所以你应该在节点被销毁时清除它。
LinkedList拥有自己的节点,所以它应该负责在没有任何内存剩余的情况下正确销毁它们。
LinkedList对象应该能够添加和删除节点,因此从所有权的角度来看,它负责清理它们,例如通过删除列表中的每个节点 反复。