C++ 移动语义与完美转发
C++11 引入的移动语义和完美转发是现代 C++ 性能优化的核心特性,它们通过减少不必要的拷贝操作显著提升了程序性能。
移动语义
左值与右值:
- 左值(lvalue):有名字、可以取地址的对象,通常在赋值运算符左边
- 右值(rvalue):临时对象、字面量、即将销毁的对象,通常在赋值运算符右边
cppint a = 10; // a 是左值,10 是右值 int b = a + 5; // b 是左值,a + 5 是右值
std::move: std::move 将左值转换为右值引用,启用移动语义。
cppstd::string str1 = "Hello"; std::string str2 = std::move(str1); // 移动构造,str1 变为空 // str1 现在处于有效但未指定的状态
移动构造函数与移动赋值运算符
移动构造函数:
cppclass MyString { private: char* data; size_t size; public: // 普通构造函数 MyString(const char* str = "") { size = strlen(str); data = new char[size + 1]; strcpy(data, str); } // 拷贝构造函数 MyString(const MyString& other) { size = other.size; data = new char[size + 1]; strcpy(data, other.data); std::cout << "Copy constructor called" << std::endl; } // 移动构造函数 MyString(MyString&& other) noexcept { data = other.data; size = other.size; other.data = nullptr; other.size = 0; std::cout << "Move constructor called" << std::endl; } // 拷贝赋值运算符 MyString& operator=(const MyString& other) { if (this != &other) { delete[] data; size = other.size; data = new char[size + 1]; strcpy(data, other.data); } std::cout << "Copy assignment called" << std::endl; return *this; } // 移动赋值运算符 MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } std::cout << "Move assignment called" << std::endl; return *this; } ~MyString() { delete[] data; } };
移动语义的优势
性能提升:
cpp// 不使用移动语义 std::vector<std::string> createVector() { std::vector<std::string> vec; vec.push_back("Hello"); vec.push_back("World"); return vec; // C++11 之前会进行深拷贝 } // 使用移动语义 std::vector<std::string> createVectorMove() { std::vector<std::string> vec; vec.push_back("Hello"); vec.push_back("World"); return vec; // C++11 之后会进行移动,避免深拷贝 } // 使用 auto vec = createVectorMove(); // 移动构造,无拷贝
容器操作优化:
cppstd::vector<MyString> vec; vec.reserve(10); MyString str1("Hello"); vec.push_back(str1); // 拷贝构造 vec.push_back(std::move(str1)); // 移动构造,更快 vec.emplace_back("World"); // 原地构造,最快
完美转发
完美转发允许函数模板将其参数完美地转发给其他函数,保持参数的值类别(左值或右值)。
std::forward:
cpptemplate <typename T> void wrapper(T&& arg) { target(std::forward<T>(arg)); // 完美转发 } void target(const std::string& str) { std::cout << "Lvalue reference: " << str << std::endl; } void target(std::string&& str) { std::cout << "Rvalue reference: " << str << std::endl; } // 使用 std::string str = "Hello"; wrapper(str); // 转发为左值引用 wrapper(std::string("World")); // 转发为右值引用
引用折叠规则:
- T& & → T&
- T& && → T&
- T&& & → T&
- T&& && → T&&
万能引用
万能引用(Universal Reference)是使用 T&& 声明的引用,可以绑定到左值或右值。
cpptemplate <typename T> void process(T&& arg) { // arg 是万能引用 if constexpr (std::is_lvalue_reference_v<T>) { std::cout << "Lvalue reference" << std::endl; } else { std::cout << "Rvalue reference" << std::endl; } } // 使用 int x = 42; process(x); // T = int&,绑定到左值 process(42); // T = int,绑定到右值
注意: 只有在类型推导上下文中 T&& 才是万能引用,否则是右值引用。
cpptemplate <typename T> class MyClass { void process(T&& arg); // 右值引用,不是万能引用 }; template <typename T> void process(T&& arg); // 万能引用
实际应用示例
智能指针工厂函数:
cpptemplate <typename T, typename... Args> std::unique_ptr<T> makeUnique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } // 使用 class Widget { public: Widget(int x, double y) : x_(x), y_(y) {} private: int x_; double y_; }; auto widget = makeUnique<Widget>(10, 3.14);
线程任务包装器:
cpptemplate <typename F, typename... Args> auto createTask(F&& f, Args&&... args) { return std::async(std::forward<F>(f), std::forward<Args>(args)...); } // 使用 void task(int x, const std::string& str) { std::cout << "Task: " << x << ", " << str << std::endl; } auto future = createTask(task, 42, "Hello");
容器插入优化:
cpptemplate <typename Container, typename T> void insert(Container& container, T&& value) { container.push_back(std::forward<T>(value)); } // 使用 std::vector<std::string> vec; std::string str = "Hello"; insert(vec, str); // 拷贝插入 insert(vec, std::string("World")); // 移动插入
noexcept 规范
移动操作应该标记为 noexcept,以便标准库在重新分配时使用移动而非拷贝。
cppclass MyClass { public: MyClass(MyClass&& other) noexcept { // 移动构造实现 } MyClass& operator=(MyClass&& other) noexcept { // 移动赋值实现 return *this; } };
最佳实践
1. 优先使用移动语义
cpp// 推荐 std::string result = std::move(tempString); // 不推荐 std::string result = tempString; // 不必要的拷贝
2. 使用 emplace 系列函数
cpp// 推荐 vec.emplace_back(args...); // 原地构造 // 不推荐 vec.push_back(Type(args...)); // 可能产生临时对象
3. 正确使用 std::forward
cpptemplate <typename T> void wrapper(T&& arg) { // 正确 target(std::forward<T>(arg)); // 错误 target(arg); // 总是左值 target(std::move(arg)); // 总是右值 }
4. 移动后对象的状态
cppstd::string str1 = "Hello"; std::string str2 = std::move(str1); // str1 处于有效但未指定的状态 // 可以安全地赋值或销毁 str1 = "New value"; // OK std::cout << str1.length(); // OK,但值未定义
5. 避免对 const 对象使用 std::move
cppconst std::string str = "Hello"; std::string str2 = std::move(str); // 不会移动,会拷贝
常见错误
1. 滥用 std::move
cpp// 错误 std::string str = "Hello"; std::cout << std::move(str); // 不必要,可能影响性能 // 正确 std::cout << str;
2. 移动后使用对象
cppstd::string str1 = "Hello"; std::string str2 = std::move(str1); std::cout << str1; // 未定义行为
3. 返回局部变量时不使用 std::move
cpp// 错误 std::string createString() { std::string str = "Hello"; return std::move(str); // 阻止 RVO } // 正确 std::string createString() { std::string str = "Hello"; return str; // 编译器会自动优化 }
4. 忘记 noexcept
cpp// 不推荐 MyClass(MyClass&& other); // 没有 noexcept // 推荐 MyClass(MyClass&& other) noexcept; // 允许标准库优化
性能对比
cpp#include <chrono> #include <vector> class BigObject { private: std::vector<int> data; public: BigObject(size_t size) : data(size) {} BigObject(const BigObject& other) : data(other.data) { std::cout << "Copy" << std::endl; } BigObject(BigObject&& other) noexcept : data(std::move(other.data)) { std::cout << "Move" << std::endl; } }; void benchmark() { const size_t size = 1000000; // 拷贝性能测试 auto start = std::chrono::high_resolution_clock::now(); std::vector<BigObject> vec1; for (size_t i = 0; i < 100; ++i) { BigObject obj(size); vec1.push_back(obj); // 拷贝 } auto end = std::chrono::high_resolution_clock::now(); auto copy_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); // 移动性能测试 start = std::chrono::high_resolution_clock::now(); std::vector<BigObject> vec2; for (size_t i = 0; i < 100; ++i) { BigObject obj(size); vec2.push_back(std::move(obj)); // 移动 } end = std::chrono::high_resolution_clock::now(); auto move_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "Copy time: " << copy_time.count() << " ms" << std::endl; std::cout << "Move time: " << move_time.count() << " ms" << std::endl; }
移动语义和完美转发是现代 C++ 的重要组成部分,正确使用它们可以显著提升程序性能,特别是在处理大型对象和容器时。