假设我有一个单独的链表,其基本构建块是
struct Node {
Data d;
Node *pNext;
// methods
~Node();
};
链表的头部存储为,
Node *m_Head; // member of some class
当我完成列表时,我会通过删除每个节点来清除它,
void Erase()
{
Node *pIter, *pTemp = m_Head;
while((pIter = pTemp) != 0)
{
pTemp = pIter->pNext;
delete pIter;
pIter = pTemp;
}
}
我想,如果我可以简化这个。所以我提出了一个想法,我可以用一条指令清理整个链表!
delete m_Head;
和析构函数看起来像:
Node::~Node() { delete this->pNext; }
我担心的是,它会导致递归(隐含地由delete
引起)?如果是,那么对于更大的链接列表来说肯定是一个问题。编译器是否能够以任何方式帮助优化它?
[注意:不使用任何类似std::list
或其他的库设施。]
答案 0 :(得分:3)
我认为您需要问的问题是,列表中的每个Node
拥有 pNext
Node
吗?如果没有,那么它没有业务在其析构函数中删除其pNext
节点。
在大多数链表实现中,所有节点都归列表所有,节点不拥有列表中的所有节点。将节点保持为哑(POD结构)并让所有逻辑都驻留在列表中更有意义。
这绝对是一种设计“气味”,你的节点有一个析构函数,但没有复制构造函数或复制赋值运算符。我认为当你来实现插入,拼接和擦除单个元素函数的代码时,这种方法将导致更多的复杂性,因为你必须在任何情况下手动管理pNext
指针,以避免无意中破坏列表的整个尾部
答案 1 :(得分:2)
当然:只是出于学习目的或当您确定自己的List真的更适合您的用例时才这样做
这取决于。可能您的编译器将检测到tail recursion并发出概念上等同于使用循环的代码。
如果没有,那么是的,它会递归。通常,如果堆栈压力很小(如您的情况),在商品盒上应该可以进行数千次递归。但是,无法保证,实际上,对于非常大的列表,这可能是一个问题。
另外,我认为递归确实不完全适合兄弟节点的概念。一个节点层次结构,就像quadtrees一样,会为递归而烦恼,但是当list-concept是关于层次结构)时有一个不太好的时间。 >同级 -nodes。
您也可以将手动循环视为一种易于实现的递归优化,使您的代码在保证的情况下更加健壮。
顺便说一句,你还可以 将删除节点删除到持有者类中:
class List {
public:
~List() {
for-each-node
delete-node
}
private:
class Node {
Node *node_;
...
};
...
};
这基本上是通常如何实现标准库的列表。它使整个实现更容易实现,并且在概念上更正确(节点不是拥有他们的兄弟在逻辑上)
答案 2 :(得分:1)
大多数编译器在默认设置下执行tail call elimination。一些更聪明的人可以将非尾调用转换为尾调用。
所以,只要你开启了一些优化,这个方法就可以了。
答案 3 :(得分:0)
原始指针?手动拨打delete
?
为什么不,简单地说:
struct Node {
Data d;
std::unique_ptr<Node> next;
};
然后你根本不用担心内存管理,这是自动的!