C++学习笔记 50 C++移动语义
移动语义
移动语义让事情变得简单,移动语义本质上允许我们移动对象。这在C++11之前是不可能的,因为C++11引入了右值引用,这是移动语义所必须的。基本思想是,当我们写C++代码时,有很多情况下,我们不需要或者不想把一个对象从一个地方复制到另一个地方,但又不得不复制,因为这是唯一可以复制的地方。
例如:
- 我们把一个对象传递给一个函数,那么它要获得那个对象的所有权,我没有选择,只能拷贝。
- 当我想从函数返回一个对象时也是一样,我仍然需要在函数中创建那个对象,然后返回它,也就是说又需要复制对象。
现在我不喜欢用返回值作为例子,因为因为有一种叫做返回值优化的东西对这部分进行了优化,让它不再是个问题。但是在第一个例子中,如果我需要传入一个对象,这个对象放入某个函数,而这个函数需要得到这个对象的所有权或者其它,我仍然需要在当前堆栈中构造一个一次性对象,不管它在哪里,然后将它复制到我正在调用的函数中,而这并不理想。因为我在这里不需要它,而是在那里需要,但我又不能在那里构造它,因为我需要先在这里构造它,然后把它传递进去,它变得一团糟。
当然,如果你的对象只是由一对整数或类似的东西组成,那复制也没什么大不了的,但如果你的对象需要堆分配内存之类的(比如:一个字符串,如果你需要复制它,你需要创建一个全新的堆空间),这就不好了,这会成为一个沉重的复制对象,这正是移动语义的用武之地。
如果我们只是移动对象,而不是复制它,那么性能会更高。可以类比剪切和复制的区别。
#include<iostream>
#include<string>
//1. 自定义一个被操作的类
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: 它不应该抛出异常,并不是经常使用它,但是为了保证绝对正确,我们会这样做
//通过指定这个构造函数,希望最终当我们执行这个“复制”构造函数时,不会复制。而是变成 移动了
String(String&& other) noexcept {
printf("Moved!\n");
m_Size = other.m_Size;
//这里不再复制,而是重新指向参数传过来的数据地址,所以没有复制
m_Data = other.m_Data;
//但是这个是移动复制构造函数,所以有2个String实例,而第二个的实际数据指针也指向第一个的数据地址。
//那么当第一个数据被删除后,会发生什么情况呢?会不会影响当前被复制出来的这个,数据指针指向之前的数据的对象呢?
//因为删除的第一个对象会把数据带走(被回收了)
//所以,移动构造函数不能在这里结束,还需要处理另外一个字符串:你当前这个新的字符串控制了它,偷走了它的数据,
//这就是所谓的偷走, 保证临时变量被充分使用,避免悬挂指针
other.m_Data = nullptr;
other.m_Size = 0;
//实际上是接管了旧的字符串,而不是通过复制所有的数据和分配新的内存来进行深度复制(深拷贝)
//实际上是做了一个浅拷贝,只是重新连接了指针,
}
~String()
{
printf("Destroyed!\n");
delete[] m_Data;
}
void Print() {
for (uint32_t i = 0; i < m_Size; i++) {
printf("%c", m_Data[i]);
}
printf("\n");
}
};
//1. 自定义一个操作类,其中引用被操作类:String,那么,被操作类的调用和传递,使用移动语义会避免复制,提升效率
class Entity {
private:
String m_Name;
public:
//这里的 m_Name(name) 调用了String的复制构造函数
Entity(const String& name) : m_Name(name) {
}
//移动语义的实现,需要这里也具备一个构造函数,只接收右值,临时值
//m_Name((String&&)name) 中,强制类型转换的(String&&)必须得加,否则还会找复制构造函数,造成一次复制
//但是一般不会生硬地写成强制类型转换的形式,而是:std::move(name),基本上做的是同样的事情。
//Entity(String&& name) : m_Name(name) {
//Entity(String&& name) : m_Name((String&&)name) {
Entity(String && name) : m_Name(std::move(name)) {
}
void PrintName() {
m_Name.Print();
}
};
int main() {
//它不是一个左值,没有赋值给任何东西,只是作为这个构造函数的一个参数
Entity entity("Cherno");
entity.PrintName();
std::cin.get();
}
/*
//1. 未有右值构造函数
Created!
Copied!
Destroyed!
Chern
//2. 有右值构造函数,但是只有
Created!
Copied!
Moved
Destroyed!
Chern
//终极目标
Created!
Moved
Destroyed!
Chern
*/

浙公网安备 33010602011771号