C++ 运算符=链接列表问题的重载

C++ 运算符=链接列表问题的重载,c++,linked-list,operator-overloading,C++,Linked List,Operator Overloading,我对链表的重载=运算符的实现有问题。列表类包含一个节点*头指针和一个结构节点,其中包含T*数据和节点*下一个,其中T是模板类型名。我对运算符函数结尾处发生的事情感到不安,在这里,析构函数(在本例中由makeEmpty处理)被运算符结尾调用了两次,一次是在遍历列表并创建相同节点的新列表之后,一次是在运算符函数退出之后 以下是makeEmpty实现: // Does the work of the Destructor template <typename T> void List<

我对链表的重载=运算符的实现有问题。列表类包含一个
节点*头
指针和一个
结构节点
,其中包含
T*数据
节点*下一个
,其中T是模板类型名。我对运算符函数结尾处发生的事情感到不安,在这里,析构函数(在本例中由
makeEmpty
处理)被运算符结尾调用了两次,一次是在遍历列表并创建相同节点的新列表之后,一次是在运算符函数退出之后

以下是makeEmpty实现:

// Does the work of the Destructor
template <typename T>
void List<T>::makeEmpty() {

cout << endl << endl << "DESTRUCTOR CALLED" << endl << endl;
List<T>::Node* tempPtr = head;

if (head != NULL) {

    List<T>::Node* nextPtr = head->next;

    for(;;) {
        if (tempPtr != NULL) {

            delete tempPtr->data;
            tempPtr = nextPtr;

            if (nextPtr != NULL)
                nextPtr = nextPtr->next;

            /*tempPtr = head->next;
            delete head;
            head = tempPtr;*/
        }

        else break;
    }
}

}
// Overloaded to be able to assign one list to another
template <typename T>
List<T> List<T>::operator=(const List& listToCopy) {

List<T> listToReturn;
listToReturn.head = NULL;
List<T>::Node* copyPtr = listToCopy.head;

List<T>::Node* thisPtr = head;

if (copyPtr != NULL && thisPtr != NULL) {
    for(;;) {
        if (copyPtr != NULL) {

            T* toInsert = new T(*copyPtr->data);
            listToReturn.insert(toInsert);

            copyPtr = copyPtr->next;
        }

        else{cout << endl << listToReturn << endl << endl; return listToReturn;}
    }
}
// if right-hand list is NULL, return an empty list
return listToReturn;
}
//析构函数的工作
模板
void List::makeEmpty(){
数据);
listToReturn.insert(toInsert);
copyPtr=copyPtr->next;
}

否则{cout你要的是指针和提示,所以我给它:

1)不必要的动态数据成员

您的
List::Node
不需要底层数据值的动态成员。它应该可以从
const T&
构造,如果实现符合C++11的移动构造习惯用法,也可以使用
T&
构造。并且两者都应该将
next
成员初始化为
nullptr

2)必须为
列表
复制构造函数

根据,您的类具有动态成员,因此必须在复制构造和赋值操作符操作中正确管理它们(或隐藏所述实现,但显然这不是您的选项,因为它是您赋值的一部分)

3)将类复制构造函数用于赋值运算符重载

重载涉及动态分配的赋值运算符(它们就在这里,因为
节点的链接列表要求这样做)理想情况下,应该利用复制构造函数和复制/交换习惯用法进行生命周期管理。这有许多好处,其中最重要的两个好处是通过优化编译器潜在地避免复制操作,以及将对象保持在其原始状态的异常安全性

4)
List::operator=
覆盖应该返回对当前对象的引用

指定给的当前对象(操作符的左侧)应该是按引用返回的结果。应该是修改的对象。这是此类运算符的标准实现的一部分。您返回一个副本,但原始对象保持不变,从而完全违背赋值运算符的目的(即,在您的实现中,左值端实际上根本没有被修改)

下面将详细介绍其中的每一项:


不必要的动态数据成员

由于它没有发布,我必须在
列表
中说一些安慰的话。我想象这样的事情:

template<class T>
class List
{
private:
    struct Node
    {
        T* data;
        Node* next;
    };
    Node *head;

    // other members and decls...
};
List<int> lst1, lst2;
lst1.insert(1);
lst2.insert(2);
lst1 = lst2; // <== this line
现在,当需要一个新节点来保存
T
对象时(例如在插入操作中),可以执行以下操作:

template<typename T>
void List<T>::someFunction(const T& obj)
{ 
    Node *p = new Node(obj);
    // ...use p somewhere...
}
我相信你看到这个会想,“嗯??”仔细看一下参数>代码> BYVA<代码>,并考虑为什么我这样命名它。它不是一个传统的<代码> const 引用,您可能已经习惯了。它是赋值表达式右手边的值副本。因此,为了创建它,编译器将生成一个新的<代码>。List
,调用copy构造函数来执行此操作。该复制的结果是临时对象
byval
,我们将其作为参数。我们所做的就是交换头指针。想想这会做什么。通过交换头指针,我们得到他的列表,他得到我们的列表。但他的是赋值表达式原始右侧的副本,以及我们的,好吧,我们希望它被删除。这正是当
byval
的析构函数在这个函数完成后被激发时会发生的事情

简言之,它生成如下代码:

template<class T>
class List
{
private:
    struct Node
    {
        T* data;
        Node* next;
    };
    Node *head;

    // other members and decls...
};
List<int> lst1, lst2;
lst1.insert(1);
lst2.insert(2);
lst1 = lst2; // <== this line
列表lst1、lst2;
lst1.插入(1);
lst2.插入(2);

lst1=lst2;//您要求提供提示和提示,因此我给出:

1)不必要的动态数据成员

您的
List::Node
不需要底层数据值的动态成员。它应该可以从
const T&
构造,如果实现符合C++11的移动构造习惯用法,也可以使用
T&
构造。并且两者都应该将
next
成员初始化为
nullptr

2)必须为
列表
复制构造函数

根据,您的类具有动态成员,因此必须在复制构造和赋值操作符操作中正确管理它们(或隐藏所述实现,但显然这不是您的选项,因为它是您赋值的一部分)

3)将类复制构造函数用于赋值运算符重载

重载涉及动态分配的赋值运算符(它们就在这里,因为
节点的链接列表要求这样做)理想情况下,应该利用复制构造函数和复制/交换习惯用法进行生命周期管理。这有许多好处,其中最重要的两个好处是通过优化编译器潜在地避免复制操作,以及将对象保持在其原始状态的异常安全性

4)
List::operator=
覆盖应该返回对当前对象的引用

指定给的当前对象(操作符的左侧)应该是按引用返回的结果。它应该是被修改的对象。这是此类运算符的标准实现的一部分。您返回一个副本,但原始对象保持不变,从而完全违背赋值运算符的目的(即左值端)
List<int> lst1, lst2;
lst1.insert(1);
lst2.insert(2);
lst1 = lst2; // <== this line