我正在尝试正确地遵循5的规则来实现一个简单的链表。我现在大约3,尽管我在这里已经有疑问,但是从那以后,我就如履薄冰了。由于这似乎是一个相当普遍的话题,我很惊讶找不到完整的示例。我找到了点点滴滴,但没有完整的设置。因此,如果我对此进行排序,它也可以作为将来的参考。
我为现实中的“复杂性”添加了一个示例class Data
,因为大多数示例仅具有一个带有单个int
和指向下一项的指针的节点。
您能帮我检查代码的正确性并完成五分法则的其余部分吗?
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, will be added when known
//Nodelist& operator=(NodeList&& src); // 5. move assignment operator, will be added when known
void AddToNodeList(Data data); // add node
private:
Node* head;
};
void NodeList::AddToNodeList(Data data)
{
head = new Node(data, head);
}
NodeList::~NodeList() // 1. destructor
{
Node* n = head, * np;
while (n != nullptr)
{
np = n->GetNext();
delete n;
n = np;
}
}
NodeList::NodeList(const NodeList& src) : head(nullptr) // 2. copy constructor
{
Node* n = src.head;
while(n != nullptr)
{
AddToNodeList(n->GetData());
n = n->GetNext();
}
}
NodeList& NodeList::operator=(const NodeList& src) // 3. assignment operator
{
std::swap(head, src.head); // don't know if this is the most efficient and robust method
return *this;
}
/* lost from here on, to be added when known
NodeList::NodeList(Nodelist&& src) : head{src.head} // 4. move constructor
{
}
Nodelist& operator=(NodeList&& src) // 5. move assignment operator
{
} */
首先要解决的是您的赋值运算符不正确。您正在使用复制/交换惯用语,但忘记进行复制。
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;
}
请注意自我分配的附加测试。确实不是必须的,但是may可以加快代码的速度(但同样,不能保证)。
关于这是否是执行此操作的最有效方法,这有待辩论-这完全取决于目标。但是可以肯定的是,如果使用复制/交换习惯,就不会有错误,悬空的指针或内存泄漏。
现在进入移动功能:
要实现缺少的功能,基本上应该从现有对象中删除内容,并从传入的对象中窃取内容:
首先,移动构造器:
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
的原因。)