【问题标题】:Override = operator linked linked c++ deep copy覆盖 = 运算符链接的链接 C++ 深拷贝
【发布时间】:2013-10-26 18:52:55
【问题描述】:

所以,我正在尝试在我正在编写的链接列表类中覆盖 operator=,但由于某种原因不断遇到这个奇怪的问题。

List& List::operator=(const List& copyList){
if(copyList.head != nullptr){
    makeEmpty();   // clears *this from any previous nodes
    cout << "if statement " << endl;
    head = new Node; // create a new node for head
    head -> data = copyList.head -> data; // copy the first data of copylist
    Node* pnew = head; // a temp node to traverse the new linkedlist
    assert(head != nullptr);
    Node* current2 = copyList.head;
    current2 = current2 -> next;
    while(current2 != NULL && pnew != NULL){
        cout << "entering while loop " << endl;
        pnew-> next = new Node;
        pnew -> next->data = current2 ->data;
        cout << "pnew next data " << *(pnew -> next->data)  << endl;
        assert(pnew-> next != nullptr);
        pnew = pnew -> next;
        current2 = current2 -> next;
        cout << "after current2" << endl;
    }
     pnew -> next = NULL;

}else{
    cout << "else statement " << endl;
    head = nullptr;
}
cout<< "printing out copylist"<< endl << copyList << endl;
cout<< "printing current list: " << endl << *this << endl;
return *this;

}

所以,这是我必须测试运算符覆盖的代码:

cout << "mylist:" << endl << mylist << endl;
   cout << "mylist4:" << endl << mylist4 << endl;
   mylist = mylist4;
   cout << "mylist:" << endl;
   cout << mylist << endl;
   cout << "mylist4:" << endl;
   cout << mylist4 << endl;

这是输出:

mylist:
10 f
16 u
20 n
25 !

mylist4:
14 s
15 t
16 u
18 f
19 f
25 !

if statement
entering while loop
pnew next data 15 t

after current2
entering while loop
pnew next data 16 u

after current2
entering while loop
pnew next data 18 f

after current2
entering while loop
pnew next data 19 f

after current2
entering while loop
pnew next data 25 !

after current2
printing out copylist
14 s
15 t
16 u
18 f
19 f
25 !

printing current list:
14 s
15 t
16 u
18 f
19 f
25 !
*crashes right here*

我已经尝试解决这个问题大约 3 天了。任何帮助将不胜感激。提前致谢!

编辑:这里是构造函数(析构函数是编译器默认的):

NodeData::NodeData(int n, char c)  { 
    num = n; ch = c; 
} 

EDIT2:我仔细检查后发现了问题。问题是我没有将头的最后一个节点,即while循环之后的pnew指向null。这解决了这个问题。感谢大家的支持。

【问题讨论】:

  • 您不会通过发布的代码获得该输出。首先是原因,它甚至不会编译。没有与else 匹配的if,而且我在这段代码中至少看到了两个主要 逻辑问题,这对您列表的其余部分没有太大希望。
  • 哦,对不起,伙计。我编辑了它。不小心漏掉了 if 语句。
  • @EdHeal 从对 Dietmar 回答的评论来看,这是一项学术练习。
  • 如果你使用编译器的默认构造函数,你的列表会泄漏内存!

标签: c++ linked-list operator-keyword


【解决方案1】:

您可以控制自己的 Node 类,它应该支持透明的复制构造和赋值运算符,或者如果需要您自己动手。鉴于此,我同意 Dietmar 的观点,即利用 copy-ctor/swap/destructor 机制是理想的方法。

如果您坚持手动执行此操作,以下是一种方法。您的实现使这比它需要的更难。

List& List::operator=(const List& copyList)
{
    List tmp;
    Node **dst = &tmp.head;
    const Node* src = copyList.head;
    while (src)
    {
        *dst = new Node(*src);     // invoke Node copy-ctor
        src = src->next;           // advance source
        (*dst)->next = nullptr;    // break link to original next
        dst = &(*dst)->next;       // move target to next pointer
    }

    // tmp now has a copy of the source list
    //  swap its head pointer with ours.
    std::swap(tmp.head, head);

    // upon return, the tmp object that now holds our
    //  original list will clean it up. we own the
    //  new list form this point on. 
    return *this;
}

工作原理

指针到指针dst 始终保存下一个要填充新节点的指针的地址。最初,它保存本地List tmp 对象的头指针的地址。 .当我们添加节点时,它会更新为始终保存最后添加的节点的next 指针的地址。通过这样做,我们会自动获得前向链接。完成复制后,tmp 现在是源的副本。然后我们用自己的交换头指针。这反过来又交换了谁拥有哪个列表。当tmp 在函数退出时被销毁时,它将与它一起销毁我们的旧列表。我们保留head 指针指向的新列表。

以上假设您使用Node 的默认copy-ctor,这意味着制作副本将复制数据值下一个链接,后者是您不想要的,因此嵌入式链接中断。如果您实际实施Node::Node(const Node&amp;) 以在复制后始终将链接设置为NULL,则可以消除链接中断。即,您的 Node 复制构造函数应如下所示:

Node::Node(const Node& arg)
    : data(arg.data)
    , next()
{
}

这确保了一个干净的(无论如何都可以复制data 一样干净)副本,不会意外链接到原始arg 下一个指针。

综上所述,Dietmar 的答案是最正确的,我对此投了赞成票。如果这对您有帮助,请考虑对其进行投票,但他很容易成为理想的解决方案。

【讨论】:

  • 非常感谢您的努力。这在一定程度上帮助我了解自己做错了什么。
  • 如果代码首先复制列表 abd 然后删除原始列表,我会赞成它!指向下一个技巧的指针很简洁。代码大致就是复制构造函数应该做的,不过...
  • @DietmarKühl 是的,我考虑过这样做。我保持原样/原样的唯一原因是 OP 的插入功能。但我完全同意您提出的按值复制/交换成语。这确实是正确的方法。如果我有时间,我会将其修改为这个答案。指针对指针非常用于列表/树插入等。我经常在 C 代码中使用它。并感谢您的评论。永远欢迎。
  • 我不建议将其更改为使用复制构造函数,而是修复异常安全问题:赋值应该是字符串异常安全的。
  • @DietmarKühl 如果您的意思是Node 的分配和构造抛出异常,您是完全正确的。正如所写的那样,它只会产生部分列表,并且可以肯定的是,原始列表将被销毁。部分将被正确终止,但这并没有多大帮助。如果还有其他内容,我可能不会看到它。
【解决方案2】:

没有仔细研究实现(当我发现 makeEmpty() 时我停下来,它确保赋值运算符在没有任何充分理由的情况下不会是强异常安全的):实现赋值运算符的最简单方法是利用复制构造函数、析构函数和编写swap() 函数的一般琐碎。除了类名之外,所有类的实现看起来都一样:

T& T::operator= (T other) {
    other.swap(this);
    return *this;
}

注意,有意按值传递参数:这是值实际发生的地方。与T const&amp; 复制相比,它的优点是在某些情况下可以省略实际复制。

【讨论】:

  • 我怎么知道 T 总是有方法 swap()?我的意思是,对于我们给出的赋值,它们没有交换方法,也没有复制构造函数;我们不能向 NodeData 添加方法。
  • @user2922605:你没有swap()一些节点数据!你为你正在实现的类调用swap(),如果它没有这样的方法,你就实现它!复制构造函数也是如此:您的列表肯定有一个复制构造函数!
  • @DietmarKühl 除了你实现了移动而不是复制,他显然是在尝试实现复制。如果他执行 'list1 = list2' 你不会期望他们交换但你的 impl 会。
  • @kfsone 副本在 by-value 参数中。然后他只是将内容(包括头指针)从一个List 对象交换到另一个对象。这将交换列表的所有权。当other 被销毁时,它取出旧列表,新列表现在属于this。这需要一个适当的 copy-ctor,但无论如何你都应该拥有它(或者至少你更好)。我更新的答案中对此进行了部分实现,在本地手动制作副本,而不是操作员在 by-val-parameter 中进行复制。
  • @kfsone:我确实在移动作业通过的论点。但是,参数是按值传递的,即,它是从赋值的右侧复制的!该分配只看到一个专门创建的临时对象,可以从中移动。这种方法的主要原因是有时可以完全省略副本。
猜你喜欢
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
  • 2011-06-08
  • 2018-04-15
相关资源
最近更新 更多