双向链表三巨头

Doubly Linked List Big Three

我在尝试让我的复制构造函数、析构函数和赋值运算符为双链表工作时遇到了很多麻烦。 我有一个名为 dlist 的 class 和一个节点 class。节点 class 包含一个私有节点 next 和 previous 以及一个数据字段。我真的被困住了,我无法终生理解如何让这些工作。如果有人甚至只是指出出了什么问题。有时我会遇到段错误,有时会遇到回溯,这取决于我在三巨头中所做的更改。

//Destructor
template<class T>
dlist<T>::~dlist(){
    node<T> *rmptr = head;
    while(head != NULL && head != tail){
        head = head -> next();
        delete rmptr;
    }
}

//Copy Constructor
template <class T>
dlist<T>::dlist(const dlist<T>& other)
{
    if(other.head == NULL){
        head = new node<T>;
        tail -> set_next(head);
    }
    else{
        head = new node<T>(other.head -> data());
        tail = new node<T>;
        head -> set_next(tail);
        tail -> set_previous(head);
        node<T> *source = other.head -> next();
        node<T> *destination = head;
        while(source != NULL && source != other.tail){
            tail -> set_next(new node<T>);
            destination -> set_next(tail);
            tail -> set_data(source -> data());
            tail = tail -> next();
            source = source -> next();
        }
    }

}

//Assignment Operator
template<class T>
dlist<T>& dlist<T>::operator =(const dlist& other){

    if(this == &other){
        return *this;
    }
    node<T> * rmptr;
    while(head != NULL){
        rmptr = head;
        head = head -> next();
        delete rmptr;
    }
    head = tail = NULL;

    node<T> *source, *destination;
    if(other.head != NULL){
        head = new node<T>(other.head -> data());
        tail = new node<T>;
        head -> set_next(tail);
        tail -> set_previous(head);
        node<T> *source = other.head -> next();
        node<T> *destination = head;
        while(source != NULL && source != other.tail){
            tail -> set_next(new node<T>);
            destination -> set_next(tail);
            tail -> set_data(source -> data());
            tail = tail -> next();
            source = source -> next();
        }
    }

    return *this;
}

这个 copy/assignment/constructor/destructor 模式是经典的(至少按照我的标准:))

  • 首先,您必须将 headtail 设置为 NULL(或 nullptr)以避免在对象刚刚创建且未使用时出现段错误。这避免了析构函数读取未初始化的 headNULL 防止析构函数做一些有害的事情
  • 其次:创建一个 destroy 实用方法,供析构函数使用(当然),也供赋值运算符使用以释放已分配的对象(否则会发生内存泄漏)。无需 copy/paste 相同的代码。我一开始以为你的删除代码没问题,但事实并非如此。

destroy 看起来像这样(修复了 WhozCraig 评论的帮助):

template<class T>
void dlist<T>::destroy()
{
    node<T> *rmptr;
    while(head != NULL && head != tail){
        rmptr = head;
        head = head -> next();
        delete rmptr;

    }
    head = tail = NULL; // set head to NULL again

}

析构函数看起来像这样:

template<class T>
dlist<T>::~dlist(){
   destroy();
} 
  • 第三次创建复制构造函数和赋值运算符使用的copy实用方法(它们共享很多相同的代码,不需要copy/paste)

copy 方法如下所示:

template <class T>
void dlist<T>::copy(const dlist<T>& other)
{
    if(other.head == NULL){
        head = new node<T>;
        tail -> set_next(head);
    }
    else{
        head = new node<T>(other.head -> data());
        tail = new node<T>;
        head -> set_next(tail);
        tail -> set_previous(head);
        node<T> *source = other.head -> next();
        node<T> *destination = head;
        while(source != NULL && source != other.tail){
            tail -> set_next(new node<T>);
            destination -> set_next(tail);
            tail -> set_data(source -> data());
            tail = tail -> next();
            source = source -> next();
        }
    }

}

您现在可以非常简单地实现复制构造函数和赋值运算符:

template<class T>
dlist<T>::dlist(const dlist<T>& other)
{
    copy(other);
}

//Assignment Operator
template<class T>
dlist<T>& dlist<T>::operator =(const dlist& other){

    if(this == &other){
        return *this;
    }
    destroy();
    copy(other);

    return *this;
}

将一个对象分配给另一个已经 "full"(也称为已分配)对象时,您不会有任何内存泄漏,并且您不会因释放未分配区域而出现段错误,因为在新对象上 headtailNULL

对于回答 "beyond" 这个问题,我深表歉意,但这种模式非常有用,未经训练的编码人员尝试像这样编写低级 类 总是会遇到同样的问题 memory-leak/crash/copy-paste 障碍。