提问人:Kevinkun 提问时间:7/23/2021 更新时间:7/23/2021 访问量:88
移动语义:执行 std::move 时不执行构造函数和赋值
Move Semantics: Constructor and Assignment are not executed when performed std::move
问:
#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 与 l2 连接后,我希望在调用 Destructor 时变为 NULL 以防止双重释放。但是,似乎我创建的移动构造函数或移动赋值都没有执行。l1.Concatenate(std::move(l2));
l2
事情是这样的:
- 我知道我不需要使用 std::move(l2),我可以简单地传递 引用 .它奏效了。但是我想练习使用移动构造函数和赋值。
l1.Concatenate(l2)
void Doublyll::Concatenate(Doublyll& l2)
- 你可以看到我评论的这段代码:我已经手动完成了它,将其设置为 NULL。它将完美地工作,不会导致双重免费。但是,如果我执行 std::move(l2),我不需要手动完成,对吧?
void Doublyll::Concatenate(Doublyll&& l2)
/*l2.head = l2.tail = nullptr;*/
l2
- 如果我像这样将旧对象移动到新对象,我的移动构造函数和移动分配实际上正在工作: 像这样
Doublyll l3(std::move(l2));
l2 = std::move(l1);
那么,为什么当我尝试这样调用它来连接两个链表时,您认为它不起作用?我以为这就像在使 l2 无法访问 (NULL) 之前将 l2 移动到 l1。l1.Concatenate(std::move(l2));
std::move 不是那样工作的吗?或者也许我的代码有问题,或者还有另一种方法仍在使用 std::move 来执行这样的功能?也许你也可以向我解释一下这个动作的语义。谢谢。
答:
0赞
Daniel McLaury
7/23/2021
#1
代码不起作用,因为您注释掉了使其工作的部分,即
l2.head = l2.tail = nullptr;
您看不到正在使用的移动构造函数或移动赋值运算符,因为您尚未编写任何使用它们的代码。你以为这里有什么东西会叫他们吗?
我不确定你的问题是什么,但我猜你对什么是移动语义有一个根本性的误解。唯一能做的就是指示允许将某些东西传递给以 a 作为参数的函数。当你编写一个函数时,你的责任是将你传递的东西中的胆量移出,并让它处于一种状态,它所能做的就是在不引起任何问题的情况下进行破坏。这正是您在手动将头部指针设置为 时所做的。std::move
T&&
T&&
nullptr
评论
0赞
Kevinkun
7/23/2021
我认为你没有正确阅读我上面的陈述。我知道代码:已起作用。但是我注释掉了,因为我认为如果使用移动语义,它会自动将对象设置为.所以我不需要手动执行此操作。l2.head = l2.tail = nullptr;
l2
NULL
0赞
Kevinkun
7/23/2021
而且我也知道是传递给一个带有参数的函数。看看我的函数:.我还创建了移动构造函数和移动赋值来处理它。std::move
&&
void Doublyll::Concatenate(Doublyll&& l2)
0赞
Daniel McLaury
7/23/2021
“而且我也知道 std::move 是传递给一个带有 && 作为参数的函数。”是的,但你似乎不明白这就是它的全部作用,因为你似乎认为写作会以某种方式调用移动构造函数或移动赋值运算符。std::move
0赞
Kevinkun
7/23/2021
那么你的意思是 std::move 不会调用移动构造函数或赋值?因为当你用它把一个对象移动到一个新的或现有的对象时,它当然会调用移动构造函数/赋值。例如:LinkedList l2(std::move(l1)) 我们将 l1 移动到 l2。那么你的观点是什么?
0赞
Daniel McLaury
7/23/2021
如果 (1) 执行调用构造函数的操作,则将调用移动构造函数;(2) 存在移动构造函数;(3) 构造函数参数是右值引用。同样,如果 (1) 您执行执行分配的操作,则将调用移动分配运算符;(2)存在移动分配操作员;(3)您要分配的东西是右值引用。
评论
std::move
std::move
A
A
A
什么是 std::move(),
什么时候应该使用它?这些都回答了你的问题吗?std::move