【问题标题】:Destructing a linked list破坏链表
【发布时间】:2018-01-05 08:11:02
【问题描述】:

我试图实现一个链表来解决算法问题。
它基本上可以工作,但是结果证明我使用了太多内存。
如果有人指出以下析构函数设计的缺陷,我将不胜感激。

template<typename T>
struct Node {
    Node(): item(0),next(0) {}
    Node(T x): item(x),next(0) {}
    T item;
    Node* next;
};

template <typename T>
struct List {
    List() : head(0),tail(0) {}
    Node<T>* head;
    Node<T>* tail;
    void insert(T x) {
        Node<T>* newNode = new Node<T>(x);
        if(head == NULL) {
            head = tail = newNode;
        } else {
            tail->next = newNode;
            tail = tail->next;
        }
    }

    void clearRecur(Node<T>* h) {
        if(h) {
            clearRecur(h->next);
            delete h;
        }
    }

    void clear() {
        if(head) {
            clearRecur(head);
        }
    }
};

【问题讨论】:

  • “太多”是多少?
  • 对于初学者来说,这里有悬空参考。您释放clearRecur 中的列表,但永远不要更改tailh 之前的元素(或头部,如果它是第一个)。这同样适用于 clear,当你完成时 - headtail 仍然设置,但已经发布。
  • 你没有析构函数。你可以制作浅拷贝。
  • @PeterHwang 你可以。但是由于Node 不拥有任何东西的所有权(不是自动分配的),因此使用非默认析构函数并不是什么大问题。
  • 只要调用 clear 我就看不到内存泄漏。由于递归清除调用需要分配内存,您可能正在使用内存,这也会将列表中的元素数量限制为最大堆栈深度。您可以尝试用简单的迭代方法替换它,看看是否有帮助。

标签: c++ memory-management data-structures


【解决方案1】:

可以递归或迭代地清除列表。

除了您的(恕我直言,正确的)版本,我使用了一种稍微不同的方法——让Node 本身“负责”删除它的尾部。这也会导致递归清除(但代码更少)。

递归清除:

template<typename T>
struct Node {
    Node(): item(), next(nullptr) {}
    Node(T x): item(x), next(nullptr) {}

    ~Node() { delete next; } // <== recursive clearing

    T item;
    Node* next;

    // Using the default copy ctor would corrupt the memory management.
    // Deleting it lets the compiler check for accidental usage.
    Node(const Node&) = delete;
    // Deleting assignment operator as well.
    Node& operator=(const Node&) = delete;
};

template <typename T>
struct List {
    List() : head(nullptr), tail(nullptr) {}
    ~List() { clear(); }
    Node<T>* head, tail;
    void insert(T x) {
        Node<T>* newNode = new Node<T>(x);
        if (head == nullptr) head = tail = newNode;
        else {
            tail->next = newNode;
            tail = tail->next;
        }
    }

    void clear() {
        delete head;
        head = tail = nullptr;
    }

    // Using the default copy ctor would corrupt the memory management.
    // Deleting it lets the compiler check for accidental usage.
    List(const List&) = delete;
    // Delete assignment operator as well.
    List& operator=(const List&) = delete;
};

就是这样,我在我们当前的项目中就是这样做的。乍一看,它似乎很简单,而且工作正常。当我们的 Beta 测试人员开始发挥作用时,我改变了主意。在现实世界的项目中,列表是如此之长,以至于递归清除耗尽了堆栈内存。 (是的——堆栈溢出。)我早该知道的!

因此,我反复进行清理——“责任”从Node 移回List。 (API 用户不会注意到这一点,因为它发生在“幕后”。)

迭代清除:

template<typename T>
struct Node {
    Node(): item(), next(nullptr) {}
    Node(T x): item(x), next(nullptr) {}
    T item;
    Node* next;
};

template <typename T>
struct List {
    List() : head(nullptr), tail(nullptr) {}
    ~List() { clear(); }
    Node<T>* head, tail;
    void insert(T x) {
        Node<T>* newNode = new Node<T>(x);
        if (head == nullptr) head = tail = newNode;
        else {
            tail->next = newNode;
            tail = tail->next;
        }
    }

    void clear() {
        while (head) {
            Node<T> *p = head; head = head->next;
            delete p;
        }
        tail = nullptr;
    }

    // Using the default copy ctor would corrupt the memory management.
    // Deleting it lets the compiler check for accidental usage.
    List(const List&) = delete;
    // Delete assignment operator as well.
    List& operator=(const List&) = delete;
};

【讨论】:

  • 由于我们不在 FP(函数式编程)领域,我建议避免(尤其是)用于列表破坏的递归。
  • @MrD 可以做尾调用优化吗?
  • @user202729 有一个关于尾调用优化的问答:SO: Which, if any, C++ compilers do tail-recursion optimization?。根据我上面的故事,VS2013 中的编译器似乎缺少它(因为这些崩溃测试是在优化的发布版本中进行的)。
猜你喜欢
  • 2012-06-12
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
  • 1970-01-01
  • 2013-12-02
  • 1970-01-01
  • 2016-03-01
  • 2012-06-14
相关资源
最近更新 更多