具有全套五规则的简单链表

Posted

技术标签:

【中文标题】具有全套五规则的简单链表【英文标题】:Simple linked list with full set Rule of Five 【发布时间】:2020-09-28 19:20:45 【问题描述】:

我正在尝试正确实现一个尊重 5 规则的简单链表。我得到了大约 3,虽然我在这里已经有我的怀疑,但从那里开始,我如履薄冰。由于这似乎是一个相当普遍的话题,我很惊讶我找不到一个完整的例子。我找到了一些零碎的东西,但没有完整的集合。因此,如果我对此进行了排序,它也可以作为未来的参考。

我添加了一个示例 class Data 来说明一些现实生活中的“复杂性”,因为大多数示例只有一个带有单个 int 的节点和一个指向下一项的指针。

编辑:我已经使用 PaulMcKenzie 如下所示的代码完成了课程,它在 VS2019 中编译正常,但在移动构造函数和赋值运算符上发出警告:C26439: This kind of function may not throw. Declare it 'noexcept' (f.6)

class Data

  public:
    int id;
    string name;
    float[5] datapoints;
;

class Node

  public:
    Node(Data d =  0 , Node* n = nullptr) : data(d), next(n) ;
    Data& GetData()  return data; 
    Node*& GetNext()  return next; 
  private:
    Data data;
    Node* next;
;

class NodeList

public:
    NodeList() :head(nullptr)               // constructor
    ~NodeList();                              // 1. destructor
    NodeList(const NodeList& src);            // 2. copy constructor
    NodeList& operator=(const NodeList& src); // 3. copy assignment operator
    NodeList(NodeList&& src);                 // 4. move constructor
    NodeList& operator=(NodeList&& src);      // 5. move assignment operator
    void AddToNodeList(Data data);            // add node
private:
    Node* head;
;

void NodeList::AddToNodeList(Data data)

    head = new Node(data, head);

NodeList::~NodeList()

    Node* n = head, * np;
    while (n != nullptr)
    
        np = n->GetNext();
        delete n;
        n = np;
    

NodeList::NodeList(const NodeList & src) : head(nullptr)

    Node* n = src.head;
    while (n != nullptr)
    
        AddToNodeList(n->GetData());
        n = n->GetNext();
    

NodeList& NodeList::operator= (const NodeList& src)

    if (&src != this)
    
        NodeList temp(src);
        std::swap(head, temp.head);
    
    return *this;

NodeList::NodeList(NodeList&& src) : headsrc.head

    src.head = nullptr;

NodeList& NodeList::operator=(NodeList&& src)

    if (this != &src)
        std::swap(src.head, head);
    return *this;

【问题讨论】:

要检查您现在拥有的代码的正确性,您应该有一个小的 main 函数来创建、复制和销毁 NodeList 对象。然后查看是否有任何内存泄漏、故障等。您应该先这样做,然后再编写其他两个缺少的函数。此外,不要为这些函数编写存根。要么完全实施它们,要么不拥有它们。原因是编译器可能会在您的测试过程中调用这些函数,不完整的移动函数可能会导致问题。例如,您当前的移动分配没有返回任何内容,这是错误的。 谢谢你们。我已经把它放在一个小程序中并修复了很多错别字。你是对的,另外两个不应该有正确的代码,但我希望稍后用希望有用的 cmets 完成它们。 【参考方案1】:

首先要解决的是您的赋值运算符不正确。您正在使用复制/交换习语,但您忘记了复制。

NodeList& NodeList::operator=(NodeList src)  

    std::swap(head, src.head);
    return *this;

注意从 const NodeList&NodeList src 作为参数的变化。这将使编译器自动为我们进行复制,因为参数是按值传递的。

如果您仍想通过 const 引用传递,则需要进行以下更改:

NodeList& NodeList::operator=(const NodeList& src) 

   if ( &src != this )
   
       NodeList temp(src);  // copy
       std::swap(head, temp.head);
   
   return *this;

请注意自我分配的附加测试。确实没有必要,但可能会加快代码速度(但同样不能保证)。

至于这是否是最有效的方法,这是有争议的——这完全取决于对象。但有一件事是肯定的——如果您使用复制/交换习语(正确),就不会有错误、悬​​空指针或内存泄漏。


现在进入移动功能:

要实现缺失的功能,你基本上应该从现有对象中删除内容,并从传入的对象中窃取内容:

一、移动构造器:

NodeList::NodeList(Nodelist&& src) : headsrc.head 

    src.head = nullptr;

我们真正想做的就是从src 中窃取指针,然后将src.head 设置为nullptr。请注意,这将使src 可破坏,因为src.head 将是nullptr(并且NodeList 的析构函数正确处理nullptr)。

现在是移动分配:

Nodelist& operator=(NodeList&& src) 

   if ( this != &src )
       std::swap(src.head, head);
   return *this;

我们检查自我分配,因为我们不想偷窃自己。实际上,我们真的没有偷东西,只是换了东西。然而,与赋值运算符不同的是,没有复制——只是内部的交换(这基本上是你之前修复的不正确的赋值运算符正在做的事情)。这允许 src 在调用 src 析构函数时销毁旧内容。

请注意,在移动(构造或赋值)之后,传入的对象基本上处于可能使对象可用或不可用的状态,或者如果不可用,则稳定(因为潜在地,传递的内部- in 对象已更改)。

调用者仍然可以使用这样的对象,但要冒使用可能处于或可能不处于稳定状态的对象的所有风险。因此对于调用者来说最安全的事情是让对象消失(这就是为什么在移动构造函数中,我们将指针设置为nullptr)。

【讨论】:

感谢您的补充和解释!我仍在尝试围绕一些解释进行思考,但会更多地研究它。该代码在 VS2019 中编译得很好,但在移动构造函数和赋值运算符上给了我警告:C26439: This kind of function may not throw. Declare it 'noexcept' (f.6). 任何线索?

以上是关于具有全套五规则的简单链表的主要内容,如果未能解决你的问题,请参考以下文章

链表一

数据结构--单链表简单代码实现(总结)

LeetCode刷题日记精选例题(附代码+链接)

数据结构线性表之实现单循环链表

408数据结构与算法—单链表

408数据结构与算法—单链表