C++中的std::move与移动赋值操作是什么?
摘要:到目前为止,我们已基本上掌握了移动语义的要点和所有基本知识,移动语义能够将一个对象移动到另一个对象上,但是我们还没有涉及其中的关键部分,或者说是两个关键部分: std::move move assignment operator (移动赋值
到目前为止,我们已基本上掌握了移动语义的要点和所有基本知识,移动语义能够将一个对象移动到另一个对象上,但是我们还没有涉及其中的关键部分,或者说是两个关键部分:
std::move
move assignment operator (移动赋值运算符)这是一个赋值操作符,当我们想把一个对象移动到一个已有的对象中时。
上一集已经讲到了移动构造函数,然而,我们还没有谈到的是,如果我们想要将现有对象移动到另一个对象中,而不是构造一个新对象,会发生什么。
String string = "Hello";
//用一个构造函数构造一个新的字符串,这个构造函数接受右值引用String&&
String dest = (String&&)string;
//等同于这句,因为赋值操作符只是在做一个隐式转换,并调用这个特定字符串构造函数,
String dest((String&&)string);
//使用移动语义更优雅,且见名知义
String dest = std::move(string);
上面的方式把这个字符串移动到了新的目的地,然而,这并不是最优雅的方法,它也不是对每个类型都适用,例如:如果我们有一个auto类型,而这个实际类型不能通过我们像这样静态地写代码来推断。这样写代码并不是很好。所以我们要做的是,我们使用一个稍微灵活一点的函数,在编译时,它会找出输入的是什么类型,这个函数就是std::move。这是一个由标准类库提供给我们的实用函数,这和强制类型转换一样,但以一种更优雅和灵活的方式。去到move的定义可以看到,它是以一种很好的模板化的方式来实现的,并且它可以正确地处理所有类型,包括常量等等。不要用上面的方式,而是应该用std::move,它看起来更优雅,因为它(move)也给出了一些上下文联系,让我们知道发生了什么。是将这个string move 到了 destination。
我想强调的是,如何写这些代码并不重要(赋值方式还是构造函数方式)
String dest((String&&)string);
String dest = std::move(string);
问题是,我们在这里创建一个新的对象,而不是在这里发生移动赋值操作。正如你所看到的:String dest ,我们把变量类型放在前面,然后是变量名,这是一个全新的对象,它正在被构造。因此,它会使用移动构造函数。这就引出了之前讲到的移动赋值运算符。
移动操作符(operator = ),只有我们把一个变量赋值给一个已有的变量时才会被调用。
//调用移动构造函数
String dest = std::move(string);
//调用赋值操作符:operator = ,运算符实际上就是一个函数。
//所以,当这里调用 = 运算符时,也就是对现有对象进行赋值时,这就像一个obj.assign(obj2) 函数一样。
//很显然,这需要发生在一个已有的变量上,而String dest = std::move(string);则不行,因为是创建了一个新的变量。
dest = std::move(string);
完整案例:
#include<iostream>
#include<string>
class String {
private:
uint32_t m_Size;
char* m_Data;
public:
String() = default;
String(const char* string) {
printf("Created!\n");
m_Size = strlen(string);
m_Data = new char[m_Size];
memcpy(m_Data, string, m_Size);
}
//拷贝构造函数
String(const String& other) {
printf("Copied!\n");
m_Size = other.m_Size;
m_Data = new char[m_Size];
memcpy(m_Data, other.m_Data, m_Size);
}
//move构造函数,只接收一个右值,意思是一个临时值
//noexcept: 它不应该抛出异常,并不是经常使用它,但是为了保证绝对正确,我们会这样做
//通过指定这个构造函数,希望最终当我们执行这个“复制”构造函数时,不会复制。
