2026/10/1 2:39:28

C++11 :右值引用的作用,引用折叠和完美转发

C++11 :右值引用的作用,引用折叠和完美转发 目录一.右值引用的作用1.1为什么要引入右值引用二.类型分类了解三.引用折叠四.完美转发一.右值引用的作用回顾左值引用的作用充当别名避免拷贝直接绑定左值操作同一块内存允许修改原始对象函数可以向外带出修改结果输出型参数作为返回值使表达式成为左值支持连续赋值如operator[]1.1为什么要引入右值引用通过重载区分左右值针对即将销毁的右值实现移动语义转移资源而不拷贝数据消除不必要的内存复制开销。比如在这个例子中传值返回需要创建临时对象然后再拷贝到外面这会造成两次构造导致资源浪费那么就有同学说了现在编译器都优化了构造减少了资源浪费就少了还有必要用到右值引用吗我们先了解为什么编译器要优化传统只有左值引用传值传参中为了避免造成资源通常用下面这种方法来写形参用引用来接收改变形参的同时就可以改变外面元素了但是这里缺少可读性所以编译器要进行优化来解决该问题再讲之前我们先来了解一下什么是拷贝构造拷贝赋值移动构造和移动赋值函数函数签名时机数据源类型核心行为拷贝构造string(const string s)创建新对象初始化阶段左值 /const 左值深拷贝新建内存复制字符移动构造string(string s)创建新对象初始化阶段右值临时、std::move资源转移swap不拷贝数据拷贝赋值string operator(const string s)对象已存在赋值覆盖左值 /const 左值释放自身旧资源深拷贝新数据移动赋值string operator(string s)对象已存在赋值覆盖右值临时、std::moveswap 转移资源无数据拷贝总结拷贝构造和拷贝赋值需要消耗资源移动构造和移动赋值不需要消耗资源给出下列代码是为了和库里做区分可帮助我们看出调用了那些构造namespace Jam { class string { public: typedef char* iterator; typedef const char* const_iterator; iterator begin() { return _str; } iterator end() { return _str _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str _size; } string(const char* str ) :_size(strlen(str)) , _capacity(_size) { //cout string(char* str)-构造 endl; _str new char[_capacity 1]; strcpy(_str, str); } void swap(string s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } // 拷贝构造 string(const string s) { //cout string(const string s) -- 拷贝构造 endl; reserve(s._capacity); for (auto ch : s) { push_back(ch); } } // 移动构造 string(string s) { //cout string(string s) -- 移动构造 endl; swap(s); } string operator(const string s) { //cout string operator(const string s) -- 拷贝赋值 endl; if (this ! s) { _str[0] \0; _size 0; reserve(s._capacity); for (auto ch : s) { push_back(ch); } } return *this; } // 移动赋值 string operator(string s) { //cout string operator(string s) -- 移动赋值 endl; swap(s); return *this; } ~string() { //cout ~string() -- 析构 endl; delete[] _str; _str nullptr; } char operator[](size_t pos) { assert(pos _size); return _str[pos]; } void reserve(size_t n) { if (n _capacity) { char* tmp new char[n 1]; if (_str) { strcpy(tmp, _str); delete[] _str; } _str tmp; _capacity n; } } void push_back(char ch) { if (_size _capacity) { size_t newcapacity _capacity 0 ? 4 : _capacity * 2; reserve(newcapacity); } _str[_size] ch; _size; _str[_size] \0; } string operator(char ch) { push_back(ch); return *this; } const char* c_str() const { return _str; } size_t size() const { return _size; } private: char* _str nullptr; size_t _size 0; size_t _capacity 0; }; // 传值返回需要拷贝 string addStrings(string num1, string num2,string str) { //string str; int end1 num1.size() - 1, end2 num2.size() - 1; // 进位 int next 0; while (end1 0 || end2 0) { int val1 end1 0 ? num1[end1--] - 0 : 0; int val2 end2 0 ? num2[end2--] - 0 : 0; int ret val1 val2 next; next ret / 10; ret ret % 10; str (0 ret); } if (next 1) str 1; reverse(str.begin(), str.end()); return str; } }注图一是在Linux上不带优化运行的结果图二是一代编译器优化的结果图三是二代编译器优化后的结果红色框外部传入函数时需要创建临时对象调用构造然后进行拷贝构造我们可以看出编译器优化后直接合二为一没有拷贝构造了绿色框函数里面临时变量的构造蓝色框传值返回时调用的是构造函数创建临时对象然后再进行拷贝构造后面可以看出不断被优化甚至最后只创建了临时对象里面的局部对象都没有创建右值引用有移动构造的场景通过观察最后一张图有人就会说既然编译器都优化了最后只剩构造那还要移动构造干什么1.不同编译器对优化也不统一有些编译器优化有些编译器不优化所以还是有必要用到移动构造的2.下面的移动赋值可以体现出右值引用的价值注这里我们可以看出虽然编译器有优化但是还是会有拷贝赋值这种情况我们来看一下有移动赋值的情况虽然这里最后也有移动赋值但是相比较于拷贝赋值移动赋值不消耗资源所以还是有价值的深拷贝的自定义类型如 vector/string/map.... 实现移动构造和移动赋值是有很大的价值的浅拷贝的自定义类型如 Date/pairint, int....不需要实现移动构造和移动赋值。二.类型分类了解三.引用折叠出现场景只有模板 / 万能引用T推导的时候才会发生引用折叠普通代码手动写int 直接编译报错不存在折叠。当类型推导产生引用叠加时遵循只要存在左值引用 结果最终就是左值引用 只有两个都是右值引用 结果才是右值引用 T →TT →TT →TT →T口诀左值引用传染遇 得 全 才是 万能模板templateclass T void Function(T t) { int a 0; T x a; //x; // 部分场景下T带const打开会报错 } int main() { // 场景1传入右值 10 Function(10); // 10是纯右值 → T推导为 int // T int → 实例化void Function(int t) // 场景2传入普通左值变量 a int a; Function(a); // a是左值 → T推导为 int // T int →【引用折叠】→ int // 实例化void Function(int t) // 场景3std::move(a) 亡值右值 Function(std::move(a)); // move(a)是右值 → T推导为 int // T int → void Function(int t) // 场景4const左值 const int b const int b 8; Function(b); // const左值 → T推导为 const int // T const int → 折叠 → const int // 实例化 void Function(const int t) // T const int所以 T x a; 得到 const int xx不可修改x编译报错 // 场景5std::move(b) const右值 Function(std::move(b)); // move(const变量) 是const右值 // T推导为 const int // T const int // T const intT x a → const int x依然不能x return 0; }万能引用推导规律背诵调用Function(arg)arg 是非 const 左值→T 类型→ 折叠 → 参数类型左值引用arg 是 const 左值→T const 类型→ 折叠 → 参数const 类型arg 是右值纯右值 /std::move 亡值→T 类型→ 参数类型arg 是 const 右值→T const 类型→ 参数const 类型传左值T 推导成引用类型传右值T 推导成普通值类型。四.完美转发