移动语义是 C++11 标准引入的一个特性,旨在通过优化资源管理改善 C++ 的效率。移动语义主要涉及通过右值引用(rvalue references)来避免不必要的深拷贝,从而减少资源的分配和释放。
1. 拷贝场景
在 C++ 中,有很多的编码场景下,会导致对象的拷贝(拷贝构造函数、拷贝赋值函数)。特别是针对临时对象、即将废弃的对象的拷贝,将会极大降低程序的效率。
#if 1 #include <iostream> #include <vector> using namespace std; class Demo { public: Demo(int n) { m_size = n; p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return; } for (int i = 0; i < 10; ++i) { p_arr[i] = 100 + i; } cout << "默认构造" << endl; } Demo(const Demo& demo) { m_size = demo.m_size; p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return; } for (int i = 0; i < 10; ++i) { p_arr[i] = demo.p_arr[i]; } cout << "拷贝构造" << endl; } Demo& operator=(const Demo& demo) { if (this == &demo) { return *this; } m_size = demo.m_size; if (p_arr != nullptr) { delete[] p_arr; p_arr = nullptr; } p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return *this; } for (int i = 0; i < 10; ++i) { p_arr[i] = demo.p_arr[i]; } cout << "拷贝赋值" << endl; return *this; } ~Demo() { if (p_arr != nullptr) { delete[] p_arr; p_arr = nullptr; } cout << "析构函数" << endl; } public: int* p_arr; int m_size; }; Demo create_demo() { Demo demo1(10); Demo demo2(15); if (demo1.m_size > demo2.m_size) { return demo1; } return demo2; } // 1. 返回局部对象 void test01() { // create_demo 返回临时对象 // 在销毁之前,需要调用拷贝构造函数 Demo demo = create_demo(); } // 2. 值方式传参 void do_logic(const Demo& demo) {} void test02() { // create_demo 返回临时对象 // 在销毁之前,会创建临时对象赋值给 do_logic 的 demo 参数 do_logic(create_demo()); } // 3. 容器操作 void test03() { vector<Demo> vec; // 将临时对象拷贝到容器中 vec.push_back(Demo(10)); } // 4. 交换两个对象 void test04() { Demo demo1(10); Demo demo2(20); swap(demo1, demo2); } // 5. 对象赋值 void test05() { Demo demo(10); // 将对象拷贝赋值给 demo 对象 demo = create_demo(); } // 6. 容器中元素赋值 void test06() { vector<Demo> vec; vec.push_back(Demo(10)); vec[0] = Demo(10); } int main() { cout << "----test01----" << endl; test01(); cout << "----test02----" << endl; test02(); cout << "----test03----" << endl; test03(); cout << "----test04----" << endl; test04(); cout << "----test05----" << endl; test05(); cout << "----test06----" << endl; test06(); return 0; } #endif
程序执行结果:
----test01---- 默认构造 默认构造 拷贝构造 析构函数 析构函数 析构函数 ----test02---- 默认构造 默认构造 拷贝构造 析构函数 析构函数 析构函数 ----test03---- 默认构造 拷贝构造 析构函数 析构函数 ----test04---- 默认构造 默认构造 拷贝构造 拷贝赋值 拷贝赋值 析构函数 析构函数 析构函数 ----test05---- 默认构造 默认构造 默认构造 拷贝构造 析构函数 析构函数 拷贝赋值 析构函数 析构函数 ----test06---- 默认构造 拷贝构造 析构函数 默认构造 拷贝赋值 析构函数 析构函数
如果我们能够将这些临时对象、或者即将废弃的对象所持有的资源进行转移、而不是拷贝,这将大大提高程序的效率。
2. 右值引用
要实现针对临时对象(右值)的特殊拷贝操作,首要一点,就是要区分左值(持久对象)对象和右值对象。在 C++11 之前,只有左值引用、常量引用(万能引用),无法精确匹配到右值场景。所以,在 C++11 中,增加了专门针对右值的右值引用,使用 &&
语法表示,从而辅助实现资源移动语义。
#if 1 #include <iostream> using namespace std; class Demo { public: Demo(int n) { cout << "默认构造" << endl; } // 既能匹配左值对象、又能匹配右值对象 // 即:无法区分左值、右值,也就无法实现分别处理 Demo(const Demo& demo) { cout << "拷贝构造" << endl; } Demo& operator=(const Demo& demo) { cout << "拷贝赋值" << endl; return *this; } ~Demo() { cout << "析构函数" << endl; } public: int* p_arr; int m_size; }; void test() { // 左值引用 Demo demo(10); Demo& r1 = demo; // 常量引用(万能引用) // Demo& r2 = Demo(20); // 非常量引用的初始值必须为左值 const Demo& r3 = Demo(20); // 右值引用 Demo&& r4 = Demo(20); // Demo&& r5 = demo; // 无法将右值引用绑定到左值 } int main() { test(); return 0; } #endif
3. 资源移动
移动语义是一种机制,它允许开发者“移动”资源(如动态内存、文件句柄等),而不是复制资源。当对象的资源被移动时,源对象的资源所有权被转移到目标对象,源对象则被置于一种有效但未指定的状态。移动语义通过移动构造函数和移动赋值运算符实现。为了能够匹配临时对象,可以在类中增加:
- 参数为右值引用的构造函数(移动构造函数)
- 参数为右值引用的赋值函数(移动赋值函数)
// 移动构造 Demo(Demo&& demo) // 移动赋值 Demo& operator=(Demo&& demo)
在这两个函数编写资源转移的相关代码。
注意两点:
- 参数为右值引用,而非常量右值引用
- 如果一个右值不再使用,也可以使用 move 函数将左值转换为右值对象,实现资源转移
#if 1 #include<iostream> #include <vector> using namespace std; class Demo { public: Demo(int n) { m_size = n; p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return; } for (int i = 0; i < 10; ++i) { p_arr[i] = 100 + i; } cout << "默认构造" << endl; } Demo(const Demo& demo) { m_size = 0; p_arr = nullptr; m_size = demo.m_size; p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return; } for (int i = 0; i < 10; ++i) { p_arr[i] = demo.p_arr[i]; } cout << "拷贝构造" << endl; } Demo& operator=(const Demo& demo) { if (this == &demo) { return *this; } m_size = demo.m_size; if (p_arr != nullptr) { delete[] p_arr; p_arr = nullptr; } p_arr = (int*)malloc(sizeof(int) * m_size); if (nullptr == p_arr) { return *this; } for (int i = 0; i < 10; ++i) { p_arr[i] = demo.p_arr[i]; } cout << "拷贝赋值" << endl; return *this; } /**************资源转移********************/ Demo(Demo&& demo) { // 获得 demo 资源所有权 p_arr = demo.p_arr; m_size = demo.m_size; // 移除 demo 资源所有权 demo.p_arr = nullptr; demo.m_size = 0; cout << "移动构造" << endl; } Demo& operator=(Demo&& demo) { if (p_arr != nullptr) { delete[] p_arr; p_arr = nullptr; m_size = 0; } // 获得 demo 资源所有权 p_arr = demo.p_arr; m_size = demo.m_size; // 移除 demo 资源所有权 demo.p_arr = nullptr; demo.m_size = 0; cout << "移动赋值" << endl; return *this; } /**********************************/ ~Demo() { if (p_arr != nullptr) { delete[] p_arr; p_arr = nullptr; } cout << "析构函数" << endl; } public: int* p_arr; int m_size; }; Demo create_demo() { Demo demo1(10); Demo demo2(15); if (demo1.m_size > demo2.m_size) { return demo1; } return demo2; } // 1. 返回局部对象 void test01() { // create_demo 返回临时对象 // 在销毁之前,需要调用拷贝构造函数 Demo demo = create_demo(); } // 2. 值方式传参 void do_logic(const Demo& demo) {} void test02() { // create_demo 返回临时对象 // 在销毁之前,会创建临时对象赋值给 do_logic 的 demo 参数 do_logic(create_demo()); } // 3. 容器操作 void test03() { vector<Demo> vec; // 将临时对象拷贝到容器中 vec.push_back(Demo(10)); } // 4. 交换两个对象 void test04() { Demo demo1(10); Demo demo2(20); swap(demo1, demo2); } // 5. 对象赋值 void test05() { Demo demo(10); // 将对象拷贝赋值给 demo 对象 demo = create_demo(); } // 6. 容器中元素赋值 void test06() { vector<Demo> vec; vec.push_back(Demo(10)); vec[0] = Demo(10); } // 7. 即将销毁的对象 void test07() { Demo demo(10); vector<Demo> vec; vec.push_back(move(demo)); cout << "旧对象:" << demo.p_arr << " " << demo.m_size << endl; } int main() { cout << "----test01----" << endl; test01(); cout << "----test02----" << endl; test02(); cout << "----test03----" << endl; test03(); cout << "----test04----" << endl; test04(); cout << "----test05----" << endl; test05(); cout << "----test06----" << endl; test06(); cout << "----test07----" << endl; test07(); return 0; } #endif