c++ - 简单的链表,带有完整的五法则

标签 c++ linked-list copy-constructor assignment-operator move-constructor

我正在尝试正确实现一个遵循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) : head{src.head}
{
    src.head = nullptr;
}
NodeList& NodeList::operator=(NodeList&& src)
{
    if (this != &src)
        std::swap(src.head, head);
    return *this;
}

最佳答案

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

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) : head{src.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可以销毁旧内容。

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

调用方仍然可以使用这样的对象,但是存在使用可能处于稳定状态或未处于稳定状态的对象的所有风险。因此,对于调用方而言,最安全的方法是让对象消失(这就是为什么在move构造函数中,将指针设置为nullptr的原因)。

关于c++ - 简单的链表,带有完整的五法则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62285576/

相关文章:

c++ - fstream 接受文件夹路径并且是 (`good()==1` )

c++ - 为什么以下默认复制语法在 C++ 中无效?

c++ - 链表复制构造函数实现细节

c++ - 将工作委托(delegate)给父类(super class)的构造函数

c++ - 绑定(bind)通用成员函数

C编程: Linked Lists

c - 结构体在 C 语言链表中不起作用

java - ADT 函数不断返回 null,我不知道为什么

c++ - 在复制构造函数中检查 "self-assignment"?

c++ - 如何找到第一个非重复元素?