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

Simple linked list with full set Rule of Five

提问人:mca2 提问时间:6/9/2020 最后编辑:mca2 更新时间:6/16/2020 访问量:350

问:

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

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

编辑:我已经用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) : head{src.head}
{
    src.head = nullptr;
}
NodeList& NodeList::operator=(NodeList&& src)
{
    if (this != &src)
        std::swap(src.head, head);
    return *this;
}
C++ 链表 复制构 Assignment-Operator 移动构造函数

评论

0赞 PaulMcKenzie 6/9/2020
为了检查你现在拥有的代码的正确性,你应该有一个创建、复制和销毁对象的小函数。然后查看是否有任何内存泄漏、故障等。在继续编写其他两个缺失的函数之前,您应该先执行此操作。此外,不要为这些函数编写存根。要么完全实现它们,要么没有它们。原因是编译器可能会在测试期间调用这些函数,不完整的移动函数可能会导致问题。例如,当前的移动分配不返回任何内容,这是错误的。mainNodeList
0赞 mca2 6/10/2020
感谢您的评论。我把它放在一个小程序中,并修复了相当多的错别字。你是对的,其他 2 个不应该在那里提供正确的代码,但我希望稍后能用希望有用的注释完成它们。

答:

1赞 PaulMcKenzie 6/10/2020 #1

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

NodeList& NodeList::operator=(NodeList src)  
{
    std::swap(head, src.head);
    return *this;
}

请注意从 a 到 作为参数的更改。这将使编译器自动为我们执行复制,因为参数是按值传递的。const NodeList&NodeList src

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

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

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

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


现在进入移动功能:

要实现缺少的函数,您基本上应该从现有对象中删除内容,并从传入的对象中窃取内容:

一、搬家构造者:

NodeList::NodeList(Nodelist&& src) : head{src.head} 
{
    src.head = nullptr;
}

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

现在进行移动分配:

Nodelist& operator=(NodeList&& src) 
{
   if ( this != &src )
       std::swap(src.head, head);
   return *this;
}

我们检查自我分配,因为我们不想从自己那里偷东西。实际上,我们真的没有偷任何东西,只是把东西换掉了。然而,与赋值运算符不同的是,它不会进行任何复制 - 只是交换内部结构(这基本上是之前修复的不正确赋值运算符正在做的事情)。这允许在调用析构函数时销毁旧内容。srcsrc

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

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

评论

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