【发布时间】:2021-07-23 04:28:38
【问题描述】:
#include <iostream>
#include <utility>
#include <vector>
class Node
{
public:
int data;
Node* prev;
Node* next;
};
class Doublyll
{
private:
Node* head;
Node* tail;
public:
Doublyll();
Doublyll(std::vector<int> V);
Doublyll(const Doublyll& source);
Doublyll(Doublyll&& src) noexcept;
Doublyll& operator=(const Doublyll& rhs);
Doublyll& operator=(Doublyll&& src) noexcept;
~Doublyll();
friend std::ostream& operator<<(std::ostream& os, const Doublyll& src);
void Concatenate(Doublyll&& l2);
};
// Default Constructor will SET head and tail to nullptr
Doublyll::Doublyll()
: head(nullptr), tail(nullptr)
{
}
// Explicit Constructor using vector
Doublyll::Doublyll(std::vector<int> V)
: head(nullptr), tail(nullptr)
{
Node** p = &head;
for (auto& value : V)
{
Node* t = new Node;
t->data = value;
if (head == nullptr)
t->prev = nullptr;
else
t->prev = tail;
t->next = nullptr;
*p = t;
p = &(t->next);
tail = t;
}
}
// Copy Constructor
Doublyll::Doublyll(const Doublyll& source)
: head(nullptr), tail(nullptr)
{
std::cout << "Copy Construcor called!\n";
Node** p = &head;
// Iterate through all Node in source linked list, copying it to new object
for (Node* tmp = source.head; tmp != NULL; tmp = tmp->next)
{
Node* t = new Node;
t->data = tmp->data;
if (head == nullptr)
t->prev = nullptr;
else
t->prev = tail;
t->next = nullptr;
*p = t;
p = &(t->next);
tail = t;
}
}
// Move Constructor
Doublyll::Doublyll(Doublyll&& src) noexcept
: head(std::exchange(src.head, nullptr)), tail(std::exchange(src.tail, nullptr))
{
std::cout << "Move Constructor called!\n";
}
// Copy Assignment Operator
Doublyll& Doublyll::operator=(const Doublyll& rhs)
{
std::cout << "Copy Assignment Operator called!\n";
// Check self assignment
if (this != &rhs)
{
Doublyll tmp(rhs);
std::swap(tmp.head, head);
std::swap(tmp.tail, tail);
}
return *this;
}
// Move Assignment
Doublyll& Doublyll::operator=(Doublyll&& src) noexcept
{
std::cout << "Move Assignment called!\n";
if (this != &src)
{
std::swap(head, src.head);
std::swap(tail, src.tail);
}
return *this;
}
// Destructor
Doublyll::~Doublyll()
{
std::cout << "Desctructor called @ address " << &head << std::endl;
Node* p = head;
Node* tmp;
while (p != nullptr)
{
tmp = p;
p = p->next;
delete tmp;
}
}
// Display using Overloading << Operator
std::ostream& operator<<(std::ostream& os, const Doublyll& src)
{
Node* tmp = src.head;
if (tmp == NULL)
std::cout << "(EMPTY)\n";
else
{
for (; tmp != nullptr; tmp = tmp->next)
std::cout << tmp->data << " ";
std::cout << std::endl;
}
return os;
}
void Doublyll::Concatenate(Doublyll&& l2)
{
// Since we have tail, we can connect it by using it
tail->next = l2.head;
l2.head->prev = tail;
// Move first Node's tail to last Node of second linked list
tail = l2.tail;
// Make l2 as NULL
/*l2.head = l2.tail = nullptr;*/
}
int main()
{
// Create an Vector
std::vector<int> v1 = { 1, 3, 5, 7, 9, 11 };
std::vector<int> v2 = { 2, 4, 6, 8 };
// Create object and linked list
Doublyll l1(v1);
Doublyll l2(v2);
// Display linked list
std::cout << l1;
std::cout << l2;
// Concatenate 2 linked list
l1.Concatenate(std::move(l2));
// Display agaian after concatenate, l1 should connect with l2. and l2 should be EMPTY
std::cout << l1;
std::cout << l2;
std::cin.get();
}
在这段代码中,我尝试连接我使用 Vector 创建的 2 个链接列表。
在 main() 中,我使用 std::move l1.Concatenate(std::move(l2)); 调用,因为最后,在 l1 与 l2 连接之后,我希望 l2 变为NULL 以防止在调用析构函数时 Double Free。但是,似乎我创建的移动构造函数或移动赋值都没有被执行。
事情是这样的:
- 我知道我不需要使用 std::move(l2),我可以通过引用
void Doublyll::Concatenate(Doublyll& l2)简单地传递l1.Concatenate(l2)。它奏效了。但我想练习使用移动构造函数和赋值。 - 您可以在
void Doublyll::Concatenate(Doublyll&& l2)中看到我评论过的这段代码:/*l2.head = l2.tail = nullptr;*/,我已经手动将l2设为NULL。它会完美运行,不会导致双重释放。但是,如果我执行 std::move(l2) 我不需要手动完成,对吧? - 如果我像这样将旧对象移动到新对象,我的移动构造函数和移动赋值实际上是有效的:
Doublyll l3(std::move(l2));和这样的l2 = std::move(l1);
那么,当我尝试像这样调用l1.Concatenate(std::move(l2)); 来连接两个链表时,为什么你认为它不起作用?我认为这就像在使 l2 不可访问(NULL)之前将 l2 移动到 l1。
std::move 不是那样工作的吗?或者我的代码可能有问题,或者还有另一种方式仍然使用 std::move 来执行这样的功能?也许你也可以向我解释一下这个移动语义。谢谢。
【问题讨论】:
-
这看起来有很多代码来演示涉及
std::move的行为。您是否尝试将其简化为更简单的东西?专注于std::move并仅保留证明您的观点所需的内容。尝试更抽象地思考。不用担心链表和相关功能;专注于您的诊断消息,这些消息表明正在调用(和未调用)什么。你有一个班级,叫它A,你想搬家。A类除了跟踪成员函数调用外没有其他功能。你能用A证明你的问题吗? -
我还建议查看How to Ask,尤其是关于在在代码之前提出问题的部分。即使没有人回答你的问题,强迫自己用语言描述你的情况通常会带来更多的理解。
-
“也许你也可以向我解释一下这个移动语义。”——如果这是你真正的问题(记住:每个问题一个问题),那么你可能是重复What is move semantics? 另一个潜在的重复:What is
std::move(), and when should it be used? 这些是否回答了您的问题? -
看看What's the difference between std::move and std::forward。这应该清楚地说明
std::move是什么以及它的用途。
标签: c++ linked-list move-semantics