当前位置: 首页 > news >正文

C++进阶--c++11(02)

文章目录

  • C++进阶--c++11(02)
    • 复习+补充
    • 右值引用和移动语义
      • 引用延长生命周期
      • 右值引用和移动语义在传参中的提效
    • 类型分类
    • 引用折叠
    • 完美转发
    • 个人学习心得
    • 结语

我们今天又见面啦,给生活加点impetus!!开启今天的编程之路!!
在这里插入图片描述
今天我们进一步完善右值引用与移动语义。
作者:٩( ‘ω’ )و260
我的专栏:C++进阶,C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!

C++进阶–c++11(02)

复习+补充

复习:
1:临时对象,匿名对象的生命周期只在当前代码的一行,编译器执行完这行代码,就会自动调用析构函数
2:从为什么会增加移动构造,移动赋值来理解移动语义?
本质就是左值在传值返回中仍然有缺点,移动构造和移动赋值是属于雪中送炭
3:注意:必须要有资源的时候才有可能写移动构造移动赋值,比如:二叉树中需要new结点,链表和vector中需要new数组。可以类比拷贝构造记忆,因为没资源也没有必要写拷贝构造,析构函数,赋值重载。
4:编译器的优化
前面在传值返回的场景中,我们是使用的Linux环境并且关闭了优化才是如此的。
如果我们不关闭优化,优化如下:
在vs2019Debug以及之前的版本:构造+拷贝构造直接优化成构造,构造+移动构造直接优化成构造,拷贝构造+拷贝构造直接优化成拷贝构造,移动构造+移动构造直接优化成移动构造,前者是明确要求的
在vs2019Release以及之后版本:直接省略了拷贝构造/移动构造,因为编译器觉得下方反正使用str去构造ret,不如直接让str变成ret需要拷贝构造的临时对象的引用,修改str就是在修改ret需要拷贝的临时变量,底层是指针(因为引用的底层就是指针),如何验证呢?我们来查看他们两个的地址是否相同:
在这里插入图片描述
但是比较新的编译器,移动赋值是无法被极致优化的。
如果是比较老的版本,就会发现这两个地址是不相同的。

分析:这里的优化会不会显得右值引用的移动语义失去意义呢?
不是的,这种优化不是c++委员会规定的,是编译器自行实现的,只有比较新的编译器才会这样搞。
即:老的编译器会有移动构造保底,性能也还不错
为什么编译器会这样优化呢?
有资源的时候需要写移动构造或拷贝构造,但是如果有资源但没有写或写错了,就是出问题,所以编译器就直接将这两个函数给优化掉了。

string addStrings(string num1, string num2)
{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());cout << "******************************" << endl;return str;
}
int main()
{string ret = addStrings("11111", "2222");return 0;
}

先来思考一个问题:
临时变量,即右值可不可以修改?
在前面的学习之中,我们说临时变量具有常性,不能被修改,但是如果不能被修改,那我们怎么转移临时变量中的资源呢?
现实与目标不符,所以,就有了下面的知识点:
我们先来说一个结论:

其实只有在常量区的内容是不能被修改的,但是临时变量是可以通过一些手段修改的。比如下面的知识点,右值引用的变量是左值,我们来引用右值,通过对右值引用变量的修改,就能够达到对临时变量的修改

如果说我们就要让临时变量不能被修改呢?
加一个const,我们修改临时变量的方法为:自定义的swap。
比如:

const string&& ret1=string("11111");
string&& ret2=string("22222");

下面就是右值引用,右值引用变量是左值,有地址,通过修改ret2中的内容,就能够完成对临时变量的修改,但是上面被const修饰,不能够修改ret1,也就不能够修改临时变量了。

注意:我们前面所提到的引用是别名,不会创建空间,实际上在底层深挖,是开了空间的,但是底层和上层描述的区别,我们认为是没有开空间的。

补充:

右值引用的变量是左值

我们可以来举一个例子:
在上一篇文章中我们提到,移动构造和移动赋值本质就是将临时对象或者是匿名对象中的资源转移到另外一个变量中。
但是我们这个交换函数和移动构造是这样写的:

//移动构造
string(string&& s)//s是左值属性
{cout << "string(const string& s) -- 移动构造" << endl;this->swap(s);
}
//交换函数
void swap(string& ss)
{::swap(_str, ss._str);::swap(_size, ss._size);::swap(_capacity, ss._capacity);
}

我们发现,为什么这个交换函数中参数传递的是左值引用呢?
就是因为右值引用变量本身是左值

move的解释:在上篇文章中,我们说明了move是可以将左值强转为右值的函数模版。可以理解为我们给这个左值添加了一个属性,赋予了这个左值的资源可能被转移的属性,但是本身不变。
例如:

int i=0;
double(i);

我们这里强转i为double,是这个生成的临时对象是一个double,但是i的属性还是不变的。

右值引用和移动语义

引用延长生命周期

前面我们所说的临时变量和匿名对象的生命周期只有当前代码的这一行,其实是有问题的,因为临时对象或匿名对象的生命周期可能会被延长。
我们来看下面一段代码:

int main()
{string&& ret1 = string("1111111");const int& ret2 = 10;/.../return 0;
}

此时临时对象和匿名对象的生命周期都被延长了,思考一下:如果不延长生命周期的话,会发生什么?

执行完了这句代码,临时对象就被销毁,那么我右值引用引用的到底是什么?就会变成野引用
所以:临时对象的生命周期会被延长到直至这个引用变量(别名)被销毁的时候
总结:右值引用能够延长临时对象的生命周期,const 左值引用也能够延长临时对象的生命周期,但是此时引用变量不能被修改

string&& ret1 = "1111111";
const string& ret2 = string("2222222222");//ret2不能被修改
//ret2+="333";//错误

右值引用和移动语义在传参中的提效

我们发现在c++11之后每个容器中的插入接口都增加了右值引用的版本。
因为当插入的是左值时,如果有资源,仍然会调用拷贝构造到容器空间中的对象。
当插入的是右值是,如果有资源,就会调用移动构造到容器空间中的对象。

我们来看下面这段代码:

int main()
{list<string> ret;string a("111111");ret.insert(a);//调用左值接口ret,insert("2222");//调用右值接口return 0;
}

在这里插入图片描述
所以如果我们自己实现的话,我们需要来书写两个函数来同时兼容左值和右值的插入。当我们学会了下面的知识点之后,其实就可以完全写一个函数。

类型分类

在c++11中,我们首先是分类了左值和右值,但是如果细分,右值还可以分为纯右值和将亡值。
纯右值:纯右值是指那些字面值常量或求值结果相当于字面值或是⼀个不具名的临时对象,在c++98之前的右值都被叫做纯右值
将亡值:返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达,如move,static_cast<X&&>(x)
泛左值包含将亡值和左值。

引用折叠

引用折叠,可以理解为引用的引用,但是如果我们直接写引用的引用是会报错的。
在这里插入图片描述
但是如果我们typedef一个引用,再去制造引用的引用就不会报错了。

int main()
{	typedef int& lref;typedef int&& rref;int n = 0;rref&& r1 = 4;lref& r2 = n;return 0;
}//不会报错

折叠规则:
只有两个都是右值引用的时候才能是右值引用,只要有一个是左值引用,就是左值引用。如果有const的话,再加一个const即可。

我们来看一下下面的代码:

template<class T>
void func(T&& t)
{/../
}
int main()
{int n = 0;const int m = 0;func<int&&>(1);func<int&&>(n);//报错,右值引用+右值引用是右值引用,不能传递左值func<int&>(1);//报错,右值引用+左值引用是左值引用,不能传递右值func<int&>(n);func<const int&&>(1);func<const int&&>(m);//报错,折叠后是右值引用//这里右值引用+const可以达到引用变量不能被修改,即这个临时变量也不鞥能够被修改了func<const int&>(1);func<const int&>(m);//推到为const的左值引用,两个都不会报错return 0;
}

上面我们是显示实例化了这个模版参数。
显示实例化就是不让你实参到形参的过程中去推导这个类型。
接下来我们不用显示实例化。
来看代码:

template<class T>
void Function(T&& t)
{int a = 0;T x = a;//x++;cout << &a << endl;cout << &x << endl << endl;
}
int main()
{Function(10);//10是右值,T推导为intint a = 0;Function(a);//a是左值,T推导为int&Function(std::move(a));//std::move(a)为右值,T被推到为intconst int b = 0;Function(b);//b是左值,但是有const,T被推到为const int&Function(std::move(b));//std::move(b)是右值,但是有cons,T被推导为const int,因为有const,所以这里不能对右值引用对象++,即上面的xreturn 0;
}

注意:如果我们上述写成的是T& t,相当于参数是左值引用,左值引用在任何时候折叠的时候都是左值引用,如果有const,可能就是const的左值引用。但是上面我们发现,写成T&& t,既有可能是左值引用,也有可能是右值引用,索性,这个叫做万能引用

细节:万能引用一定是函数模版,即这个函数一定是自己带着自己的模版参数的

我们来举例:比如我们手动实现list的时候,我们如果不写模版参数,就使用这个

void Function(T&& t)
{int a = 0;T x = a;//x++;cout << &a << endl;cout << &x << endl << endl;
}

就说明这个T只能够是类中推出来的,即使用类模版推出来的,而不是我自己传参给推出来的。

引用折叠的目的:制造引用折叠及其规则,目的就是为了创建万能引用,这样就能够实现一个函数模版既能够兼容左值,也能够兼容右值。
实现更加泛型函数模版

完美转发

我们前面提到,右值引用变量是左值,如果我们使用万能引用,如果传过来的是左值,那我们就可以直接继续向深处调用函数,如果传过来的是右值,但是右值引用变量是左值,如果想要继续往深处调用函数,就必须将左值转换为右值,就需要使用move,但是问题来了,我知道这是谁左值还是右值吗?
来看这个代码:

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const左值引⽤" << endl; }
void Fun(int&& x) { cout << "右值引⽤" << endl; }
void Fun(const int&& x) { cout << "const右值引⽤" << endl; }
template<class T>
void Function(T&& t)
{Fun(t);
}
int main()
{Function(10);int a;Function(a);Function(std::move(a));const int b = 8;Function(b);Function(std::move(b));return 0;
}

由于右值引用变量是左值,所以如果是这样再去往深处调用的话,结果一直都是左值调用或const左值调用。
在这里插入图片描述
如果我去move的话,结果就全都是右值引用或者是const右值引用。
在这里插入图片描述
我要怎样才能够来区分呢?
此时我们需要使用到完美转发。
完美转发也是一个函数模版。
在这里插入图片描述
可以等价于
在这里插入图片描述
如果传过来的是右值,推到为值类型本身,此时再来填入,就是右值引用
如果传过来是左值,推导为左值引用,引用折叠之后就是左值引用。

如何使用呢?

一定要显示传递模版参数,目的是为了来识别左值或者是右值。
如果识别为右值,底层就会将左值给强转为右值(因为右值引用变量本身是左值),如果识别为左值,就不会强转。

所以上述代码可以改为:

template<class T>
void Function(T&& t)
{Fun(forward<T>(t));//显示传递推导的模版参数过去
}

再来看结果:
在这里插入图片描述
刚好符合我们的需求。
完美转发的目的:左值就保持右值引用变量的属性,右值就将右值引用变量强转为右值

个人学习心得

强转也会生成临时对象,但是move的强转和普通的强转有区别。
强制类型转换会生成一个临时对象,例如:int a = 0;double(a);
就是临时对象里面的a已经是double型了,但是a本身不是double型,还是int型

如果我move一个左值产生的临时对象是右值,但是变量本身还是左值,那他的被赋予的可以被抢夺资源的属性应该只能是赋值给本身了,不然自己的资源就转移不了,转移的是临时对象的资源。

结语

感谢大家阅读我的文章,不足之处欢迎指正,感谢大家支持!!
夫志当高远,如鹏举万里,抟风九霄。虽道阻且长,亦当锲而不舍,以勤为径,以苦作舟。若夫朝乾夕惕,笃行不怠,何患事之不成?
在这里插入图片描述

http://www.xdnf.cn/news/617833.html

相关文章:

  • 【算法】: 前缀和算法(利用o(1)的时间复杂度快速求区间和)
  • 全球复合铁路枕木市场深度分析:技术革新与区域增长潜力(2024-2031)
  • IIS部署微信支付模块问题
  • 欧拉公式的历史脉络、数学证明和现代意义
  • 信息学奥赛及各种程序设计竞赛中常见的名词解释
  • Android四大组件学习总结
  • PyQt学习系列07-数据库操作与ORM集成
  • JavaMail的使用
  • 重读《人件》Peopleware -(12-1)Ⅱ 办公环境 Ⅴ 大脑时间与身体时间(上)
  • 超简单 FishSpeech 本地部署
  • 【游戏设计】游戏玩法与游戏机制
  • 决策树引导:如何选择最适合你的机器学习算法
  • 文章记单词 | 第110篇(六级)
  • Java 8 Lambda 表达式使用说明与案例
  • 前端测试简介
  • Python排序函数全面指南:从基础到高级
  • 字符编码详解:ASCII、Latin1、Unicode、UTF-8 与 GBK
  • 365打卡第N1周: one-hot编码案例
  • 【数据反哺运营】用Python构建可落地的商品结构分析方法论-某朴超市
  • 【风控】申请评分卡(A卡)模型
  • QString 写时拷贝简介
  • 2025年电工杯B题思路讲解问题一四种算法
  • Java 集合框架核心知识点全解析:从入门到高频面试题(含 JDK 源码剖析)
  • 解决:dpkg: error: dpkg frontend lock is locked by another process
  • Coze工作流-变量聚合模块的应用
  • IEEE 流程
  • OSS对象存储如何避免被攻击恶意刷流量?
  • QT中延时的用法及定时器的用法
  • 异地容灾、热备与冷备:核心概念解析、技术对比及行业解决方案指南
  • 在Android APK中使用WebView加载Vue项目并实现文件导出