【C++八股】【欧弟求职】左值、右值、右值引用
左值、右值、右值引用一、什么是左值、右值、右值引用类型特点示例左值lvalue有名字、可取地址int a 10;右值rvalue临时对象、不可取地址10,a b 右值引用就是专门绑定右值的引用二、为什么需要右值引用核心问题减少拷贝提高性能典型问题没有右值引用std::vectorint getVec() { std::vectorint v {1,2,3}; return v; // 发生拷贝旧标准 }引入右值引用后移动语义std::vectorint getVec() { std::vectorint v {1,2,3}; return v; // 触发移动move } 不再拷贝数据而是“偷资源”指针转移VS 返回值优化RVO 只是“优化机会”而移动语义是“语义保证”——两者不是替代关系而是互补关系。关键问题RVO 不是一定发生返回值优化RVO / NRVOstd::vectorint func() { std::vectorint v {1,2,3}; return v; // 可能触发 NRVO } 编译器可以直接在调用方构造对象避免拷贝 / 移动以下情况可能失效1. 多返回路径 2. 返回不同对象 3. 编译器优化关闭 / debug 直接退化为拷贝构造C03或移动构造C11机制性质是否必须RVO编译器优化❌Move语言语义✅深拷贝与浅拷贝问题本质浅拷贝 裸指针假设一个类class Buffer { public: char* data; Buffer(size_t size) { data new char[size]; } ~Buffer() { delete[] data; } };默认拷贝行为危险点Buffer b1(100); Buffer b2 b1; // 默认拷贝构造浅拷贝 实际发生的是b1.data ----\ --- 同一块内存 b2.data ----/二、会出现什么问题1. double free最严重{ Buffer b1(100); Buffer b2 b1; } // 离开作用域执行顺序~b2() → delete data ~b1() → delete data再次释放 结果❌double free / heap corruption / 程序崩溃2. 悬空指针dangling pointerBuffer b1(100); Buffer b2 b1; delete[] b1.data; // 手动释放或 b1 析构 此时b2.data 指向已释放内存再访问b2.data[0] a;❌未定义行为UB三、为什么“浅拷贝”是根因默认拷贝构造Buffer(const Buffer other) { data other.data; // 仅复制指针 } 没有复制资源只是复制“地址”四、传统解决方案深拷贝Buffer(const Buffer other) { data new char[100]; memcpy(data, other.data, 100); } 每个对象独立资源✔ 解决 double free❌ 但代价大性能差五、移动语义的作用关键你的理解要改成这一句移动语义不是“替代深拷贝”而是“避免不必要的深拷贝”移动构造的正确写法Buffer(Buffer other) noexcept { data other.data; other.data nullptr; } 发生了什么资源所有权转移 b1 → b2 b1.data nullptr效果操作行为拷贝深拷贝复制数据移动转移指针不复制数据六、移动语义解决了什么问题✔ 避免重复释放Buffer b2 std::move(b1);结果b2.data → 原资源 b1.data → nullptr 析构时b2 释放资源 ✔b1 不释放安全 ✔✔ 避免大对象拷贝性能优化特别是网络 buffer报文结构vector / string七、但注意移动语义不能解决所有问题你这句话需要修正❌ “可以不用深拷贝”✅ 正确是该拷贝时仍然要深拷贝只是在可以“转移所有权”的场景下使用移动举个反例Buffer b1(100); Buffer b2 b1; // 这里必须深拷贝 因为b1 还要继续使用不能“偷走资源”八、标准做法Rule of Five只要类里有裸指针 / 资源句柄你就应该实现~Buffer(); // 析构 Buffer(const Buffer); // 拷贝构造 Buffer operator(const Buffer); // 拷贝赋值 Buffer(Buffer); // 移动构造 Buffer operator(Buffer); // 移动赋值三、移动语义Move Semantics核心思想资源不复制直接转移所有权1. 移动构造函数class A { public: int* data; A(int val) { data new int(val); } // 移动构造 A(A other) { data other.data; other.data nullptr; // 防止重复释放 } }; 本质左值复制右值转移2. std::movestd::move(x)⚠️ 重点std::move 不移动它只是把左值“强转”为右值int a 10; int b std::move(a);四、右值引用的几个关键特性1. 右值引用变量本身是左值int x 10; int y x; // 合法 因为 x 有名字 → 是左值2. 绑定规则表达式能否绑定T绑定左值✅T绑定右值❌T绑定右值✅T绑定左值❌除非 std::move五、完美转发Perfect Forwarding配合T // 万能引用forwarding reference示例templatetypename T void func(T arg) { process(std::forwardT(arg)); } 关键点传入T 推导arg 类型左值TT → T右值TTstd::forward保持“值类别”六、面试高频点总结必须会1. std::move 和 std::forward 区别std::movestd::forward作用强制转右值保持原值类型用途移动语义完美转发2. 万能引用 vs 右值引用T // 在模板中 → 万能引用 int // 普通右值引用3. 移动后对象状态std::string s abc; auto t std::move(s); s仍然有效valid但内容未定义一般为空4. 什么时候触发移动返回局部变量std::move容器扩容临时对象赋值