【C++】移动语义和完美转发
左值 (lvalue)它是在内存中有明确存储地址、可以被寻址的值。如果你可以对一个表达式取地址使用运算符那么它就是一个左值。左值通常是持久的在它所在的定义域结束之前一直存在左值引用Lvalue Reference本质上就是给一个现有的左值起了一个“别名”左值引用定义即初始化。普通左值引用 (T)只能绑定到非 const 左值。常量左值引用 (const T)可以绑定到一切左值、const 左值、右值。const int temp 10; //编译器会在内存中产生一个临时变量存储 10。 //temp 绑定到这个临时变量上。 //这个临时变量的寿命会变得和引用 temp 一样长。右值 (rvalue)右值就是那些临时出现、没有持久名字、无法取地址的值。如果你无法对一个表达式使用取地址运算符或者它是一个即将销毁的临时对象它就是右值。右值通常是“瞬时”的在包含它的表达式执行完之后它就会被立即销毁.右值引用Rvalue Reference一种绑定到右值临时对象的引用类型。int rref 10;std::move并不移动任何东西。它的唯一作用是强制将一个左值转为右值引用。template typename T typename std::remove_referenceT::type move(T t) { return static_casttypename std::remove_referenceT::type(t); }std::remove_referenceT::type这是一个类型萃取工具。无论T是int、int还是int它都能把修饰符去掉只留下纯粹的底层类型int。static_cast将输入变量t强制转换为该类型的右值引用即typestd::forward如果原始参数是左值转发后仍然是左值。如果原始参数是右值转发后仍然是右值。template typename T T forward(typename std::remove_referenceT::type t) noexcept { return static_castT(t); }移动语义Move Semantics本质是资源所有权的转移即将一个临时对象右值持有的资源转移来避免昂贵的深拷贝操作。是由类实现的功能通过移动构造函数完美转发Perfect Forwarding是在函数模板中将参数原封不动地转发给另一个函数同时完全保留参数的所有属性包括它是左值还是右值、是否带有const或volatile修饰符。template typename T T forward(typename std::remove_referenceT::type t) noexcept { return static_castT(t); }如果原始参数是左值转发后仍然是左值。如果原始参数是右值转发后仍然是右值。万能引用使用符号必须发生模板类型推导通常出现在template typename T之后的T不能加const。形式必须完全匹配必须是具体的T不能有const或std::vectorT等修饰。万能引用是完美转发的“门”。它把参数原封不动地领进来无论是左值还是右值然后配合std::forward把它原封不动地送出去。引用折叠是 C 编译器在处理“引用的引用”时遵循的一套自动简化规则。只要有左值引用参与结果就是左值引用只有全是右值引用时结果才是右值引用。内容左值内存中有明确存储地址、可以被寻址的值int a 10; // a 是左值有名字可取地址 a 20; // a 在左边OK int* p a; // p 是左值 *p 30; // *p解引用结果是左值 int** pp (*p); // 我们可以对 (*p) 再次取地址 可以看到*p是可以取地址的 const int b 5; // b 是左值虽然不可修改但它有内存地址是具名变量 void func(int x) { // 这里的 x 类型是右值引用但 x 本身是一个有名字的变量 // 所以在函数内部x 是一个左值 int* p x; // 这是合法的 }左值引用对左值的引用即给左值起别名必须初始化。int a 10; int ref a; // ref 是 a 的左值引用 const int r3 10; // 正确 std::cout a b c; // 每次调用 都返回 cout 的左值引用右值没有固定地址、没有名字的值通常是临时结果、字面量或即将销毁的对象。10; // 纯右值字面量 a b; // 纯右值运算的中间结果没有名字 func(5); // 如果 func 返回一个值非引用func(5) 就是右值 std::string(Hi); // 纯右值临时构造的匿名对象右值引用绑定到右值上。 它的符号是int r1 10; // 正确10 是右值 int a 10; int r3 std::move(a); // 正确std::move 把左值转成了右值将亡值 int n 5; // int r n; // 错误n 返回的是修改后的 n 本身有地址 int r n; // 正确n 返回的是一个临时副本旧值 5n 本身已变万能引用引用折叠templatetypename T void func(T param); // 这是一个万能引用 //场景 A传入左值变量 int a 10; func(a); //因为 a 是左值根据万能引用的特殊推导规则编译器将 T 推导为 int。 //为什么不能是int;因为如果为 int函数变为 void funcint param 这个函数只能接收右值所以编译器只能推导为int //代入模板函数签名变成 void func(int param);。 //引用折叠根据规则int 含有左值引用折叠为 int。 //最终形态void func(int param); —— 成功以左值引用的方式接收了变量。 //场景 B传入右值字面量 20 func(20); //推导因为 20 是右值编译器将 T 推导为 int。 //模板函数签名变成 void func(int param);。 //折叠没有冲突或者看作 int保持 int。 //最终形态void func(int param); —— 成功以右值引用的方式接收了临时变量移动语义我们将一个临时对象赋值给另一个对象时会触发深拷贝。比如一个包含 很多数据的std::vector拷贝它需要重新分配内存再复制数据这非常耗时。移动语义允许我们直接获取临时对象的资源只需修改指针指向而不必重新分配内存。#include iostream #include vector #include utility class MyBuffer { private: int* data; // 唯一的私有属性 public: // 构造函数 explicit 防止隐式转换 //MyBuffer b2 100; ❌ 编译错误不能隐式转换 explicit MyBuffer(int value) : data(new int(value)) { std::cout 分配内存并存入: *data std::endl; } // 析构函数 ~MyBuffer() { if (data) { delete data; data nullptr; std::cout 释放内存 std::endl; } } // --------------------------------------------------------- // 拷贝构造函数 (Copy Constructor) - 深拷贝 // --------------------------------------------------------- MyBuffer(const MyBuffer other) : data(other.data ? new int(*other.data) : nullptr) { std::cout 深拷贝数据: (data ? *data : 0) std::endl; } // --------------------------------------------------------- // 移动构造函数 (Move Constructor) // --------------------------------------------------------- MyBuffer(MyBuffer other) noexcept : data(other.data) { other.data nullptr; std::cout 资源所有权已转移 std::endl; } // --------------------------------------------------------- // 移动赋值运算符 (Move Assignment Operator) // --------------------------------------------------------- MyBuffer operator(MyBuffer other) noexcept { std::cout 执行移动赋值 std::endl; if (this ! other) { delete data; // 1. 释放当前对象持有的旧内存 data other.data; // 2. 接管新资源 other.data nullptr; } return *this; } // 访问器方法 int getValue() const { return data ? *data : 0; } void setValue(int value) { if (data) { *data value; } else { data new int(value); } } // 检查是否拥有资源 bool isEmpty() const { return data nullptr; } }; int main() { std::cout 测试移动语义 std::endl; // 测试移动构造函数 MyBuffer b1(100); MyBuffer b2(std::move(b1)); // 触发移动构造 // b1 现在是有效但未指定状态 std::cout b1是否为空: b1.isEmpty() std::endl; std::cout b2的值: b2.getValue() std::endl; // 测试移动赋值 MyBuffer b3(300); MyBuffer b4(400); std::cout \n移动赋值前 - b3: b3.getValue() , b4: b4.getValue() std::endl; b4 std::move(b3); std::cout 移动赋值后 - b3是否为空: b3.isEmpty() std::endl; std::cout 移动赋值后 - b4: b4.getValue() std::endl; return 0; }