我有一些链接列表代码,并且一切正常,直到在析构函数中创建pop_front函数。该代码在其他任何地方都可以使用,我尝试打印链接列表以查看它是否正确创建。这是我的代码中唯一被pop_front调用的部分,也是我正在释放任何内容的唯一部分

template <typename T>
class DList {
    Node<T>* head;
    Node<T>* tail;
public:
    DList() {
        head = nullptr;
        tail = nullptr;
    }

    void push_front(T newData) {
        Node<T>* newNode = new Node<T>(newData, head, nullptr);
        if (head) {
            head->prev = newNode;
        }
        else {
            tail = newNode;
        };
        head = newNode;
    }
    void push_front(DList<T> newList) {

        newList.tail->next = head;
        head->prev = newList.tail;
        head = newList.head;

    }
    void pop_front() {
        if (head) {
            Node<T>* pop = head;
            head = pop->next;
            if (head) {
                head->prev = nullptr;
            }
            else {
                tail = nullptr;
            }
            delete pop;
        }
    }
    ~DList() {
        while (head) {
            pop_front();
        }
    }
};

最佳答案

您的DList类(可能还有模板类Node,我们也看不到它的定义)没有遵循rule of three/five。您定义了析构函数,但没有定义拷贝构造函数或拷贝赋值运算符。

这意味着完全复制DList的副本(几乎肯定会调用DList::push_front(DList<T>))将导致析构函数中的“释放后使用”。

请记住,编译器生成的副本构造函数和副本赋值运算符只是执行值的成员复制。在这种情况下,您的值就是指针。因此,DList对象的副本将简单地将原始指针复制到新对象。

当这些对象之一被销毁时,列表将被删除,但是另一个对象仍将保留指向已释放的内存分配的指针。

如果希望您的类型是可复制的,则必须实现复制构造函数和复制赋值运算符,因为编译器生成的版本会做错事。如果使用的是C ++ 11,则还应该定义move构造函数和move赋值运算符。

在C ++ 11中,您还可以删除这些构造函数和运算符,然后在尝试制作副本的所有地方都会出现编译时错误:

DList(DList const &) = delete;
DList(DList &&) = delete;

DList & operator=(DList const &) = delete;
DList & operator=(DList &&) = delete;


没有Node模板类的定义,我不能建议实现复制构造函数或复制赋值运算符。但是,我可以建议move变体的一种实现:

void swap(DList & other) {
    std::swap(head, other.head);
    std::swap(tail, other.tail);
}

DList(DList && other) : head(nullptr), tail(nullptr) {
    swap(other);
}

DList & operator=(DList && other) {
    swap(other);
    return *this;
}




另一个错误:

void push_front(DList<T> newList)


应该:

void push_front(DList<T> & newList)


因为您修改了“新列表”,所以修改副本没有多大意义。但是,其语义含义与push_front(T)不匹配!

单值重载采用一个值并将其推入此列表。

列表重载获取另一个列表,并将该列表推入另一个列表。对于列表重载而言,将另一个列表推送到该列表上会更有意义,因为它将镜像单值重载的行为(“将其添加到此列表中,无论其含义如何”)。

此方法的实现也存在缺陷,因为它没有将列表的任何一个指针都设置为null,这意味着您要设置另一个“事后使用”。

10-08 15:40