C++右值、右值引用、移动语义move、完美转发forward
右值、右值引用、移动语义move、完美转发forward
内容参考:一文读懂C++右值引用和std::move - 知乎 (zhihu.com)
右值
左值可以取地址、位于等号左边;而右值没法取地址,位于等号右边。临时对象是右值
const左值引用不会修改指向值,因此可以指向右值,这也是为什么要使用const &
作为函数参数的原因之一,如std::vector
的push_back
:
右值引用
右值引用的标志是&&
,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值:
int &&ref_a_right = 5; // ok int a = 5; int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值 ref_a_right = 6; // 右值引用的用途:可以修改右值
移动语义move
可以通过移动语义std:move使右值引用指向左值其实现等同于一个类型转换:static_cast<T&&>(lvalue)
,右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值:
int a = 5; // a是个左值 int &ref_a_left = a; // 左值引用指向左值 int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向 cout << a; // 打印结果:5
int &&ref_a = 5; ref_a = 6; 等同于以下代码: int temp = 5; int &&ref_a = std::move(temp); ref_a = 6;
在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。 在拷贝构造和赋值重载函数中直接偷掉传入参数的资源,避免了数据的拷贝。
class Array { public: ...... // 优雅 Array(Array&& temp_array) { data_ = temp_array.data_; size_ = temp_array.size_; // 为防止temp_array析构时delete data,提前置空其data_ temp_array.data_ = nullptr; } public: int *data_; int size_; };
示例如
// 例:std::vector和std::string的实际例子 int main() { std::string str1 = "aacasxs"; std::vector<std::string> vec; vec.push_back(str1); // 传统方法,copy vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串 vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值 vec.emplace_back("axcsddcas"); // 当然可以直接接右值 } // std::vector方法定义 void push_back (const value_type& val); void push_back (value_type&& val); void emplace_back (Args&&... args);
可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move
触发移动语义,提升性能。
完美转发 std::forward
和std::move
一样,它的兄弟std::forward
也充满了迷惑性,虽然名字含义是转发,但他并不会做转发,同样也是做类型转换.
与move相比,forward更强大,move只能转出来右值,forward左右值都可以。
std::forward
(u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。
举个例子,有main,A,B三个函数,调用关系为:main->A->B
:
void B(int&& ref_r) { ref_r = 1; } // A、B的入参是右值引用 // 有名字的右值引用是左值,因此ref_r是左值 void A(int&& ref_r) { B(ref_r); // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败 B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过 B(std::forward<int>(ref_r)); // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值 } int main() { int a = 5; A(std::move(a)); }
例2
void change2(int&& ref_r) { ref_r = 1; } void change3(int& ref_l) { ref_l = 1; } // change的入参是右值引用 // 有名字的右值引用是 左值,因此ref_r是左值 void change(int&& ref_r) { change2(ref_r); // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败 change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过 change2(std::forward<int &&>(ref_r)); // ok,std::forward的T是右值引用类型(int &&),符合条件b,因此u(ref_r)会被转换为右值,编译通过 change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过 change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int &),符合条件a,因此u(ref_r)会被转换为左值,编译通过 // 可见,forward可以把值转换为左值或者右值 } int main() { int a = 5; change(std::move(a)); }
emplace
在 C++11
之后,stl
容器中添加了新的方法:emplace_back()
,和 push_back()
一样的是都是在容器末尾添加一个新的元素进去,不同的是 emplace_back()
在效率上相比较于 push_back()
有了一定的提升。
直接插入对象,emplace和push没有区别,但是给emplace传入对象构造所需要的参数,他会直接在容器底层构造对象,不在生成临时对象进行拷贝
v.emplace_back(10); v.emplace_back(30, 40);
可变参函数模板
template<typename... Types> void emplace_back(Types&&... args) { //不管左值引用还是右值引用,它本身是左值,传递的过程中要保持args的类型,需要完美转发 allocator_.construct(vec_ + idx_, forward<Types>(args...)); idx_++; } template<typename... Types> void construct(T* ptr, Types&&... args) { new(ptr)T(std::forward<Types>(args)...); }
引用折叠
所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。
规则就是:
如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。
也就是说,int& &&
等价于int &
//引用折叠 实参:左值Test template<typename... Types> void emplace_back(Types&&... args)//万能引用 { //Test&+&& -> Test& }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)