C++学习笔记 50 C++移动语义

移动语义

    移动语义让事情变得简单,移动语义本质上允许我们移动对象。这在C++11之前是不可能的,因为C++11引入了右值引用,这是移动语义所必须的。基本思想是,当我们写C++代码时,有很多情况下,我们不需要或者不想把一个对象从一个地方复制到另一个地方,但又不得不复制,因为这是唯一可以复制的地方。
例如

  1. 我们把一个对象传递给一个函数,那么它要获得那个对象的所有权,我没有选择,只能拷贝。
  2. 当我想从函数返回一个对象时也是一样,我仍然需要在函数中创建那个对象,然后返回它,也就是说又需要复制对象。

    现在我不喜欢用返回值作为例子,因为因为有一种叫做返回值优化的东西对这部分进行了优化,让它不再是个问题。但是在第一个例子中,如果我需要传入一个对象,这个对象放入某个函数,而这个函数需要得到这个对象的所有权或者其它,我仍然需要在当前堆栈中构造一个一次性对象,不管它在哪里,然后将它复制到我正在调用的函数中,而这并不理想。因为我在这里不需要它,而是在那里需要,但我又不能在那里构造它,因为我需要先在这里构造它,然后把它传递进去,它变得一团糟。

    当然,如果你的对象只是由一对整数或类似的东西组成,那复制也没什么大不了的,但如果你的对象需要堆分配内存之类的(比如:一个字符串,如果你需要复制它,你需要创建一个全新的堆空间),这就不好了,这会成为一个沉重的复制对象,这正是移动语义的用武之地。

    如果我们只是移动对象,而不是复制它,那么性能会更高。可以类比剪切和复制的区别。

#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
*/
posted @ 2026-01-03 17:10  超轶绝尘  阅读(6)  评论(0)    收藏  举报