正确的擦除链表的方法

时间:2011-08-30 06:54:59

标签: c++ recursion linked-list memory-management

假设我有一个单独的链表,其基本构建块是

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或其他的库设施。]

4 个答案:

答案 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;
};

然后你根本不用担心内存管理,这是自动的!