C语言中如何移动语义为?

摘要:移动语义 移动语义让事情变得简单,移动语义本质上允许我们移动对象。这在C++11之前是不可能的,因为C++11引入了右值引用,这是移动语义所必须的。基本思想是,当我们写
移动语义 移动语义让事情变得简单,移动语义本质上允许我们移动对象。这在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 */