详解—C++右值引用

目录

一、右值引用概念

二、 左值与右值

三、引用与右值引用比较

四、值的形式返回对象的缺陷

五、移动语义

六、右值引用引用左值

七、完美转发

八、右值引用作用


一、右值引用概念

C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
int main()
{int a = 10;int b = 20;Swap(a, b);
}

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。
 

int Add(int a, int b)
{return a + b;
}
int main()
{const int&& ra = 10;// 引用函数返回值,返回值是一个临时变量,为右值int&& rRet = Add(10, 20);return 0;
}

为了与C++98中的引用进行区分,C++11将该种方式称之为右值引用。
 

二、 左值与右值

左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边的,或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是也不一定完全正确。


int g_a = 10;
// 函数的返回值结果为引用
int& GetG_A()
{return g_a;
}
int main()
{int a = 10;int b = 20;// a和b都是左值,b既可以在=的左侧,也可在右侧,// 说明:左值既可放在=的左侧,也可放在=的右侧a = b;b = a;const int c = 30;// 编译失败,c为const常量,只读不允许被修改//c = a;// 因为可以对c取地址,因此c严格来说不算是左值cout << &c << endl;// 编译失败:因为b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值//b + 1 = 20;GetG_A() = 100;return 0;
}

因此关于左值与右值的区分不是很好区分,一般认为:

1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值。
3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
4. 如果表达式运行结果或单个变量是一个引用则认为是左值。

总结:

1. 不能简单地通过能否放在=左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质判断,比如上述:c常量
2. 能得到引用的表达式一定能够作为引用,否则就用常引用。

C++11对右值进行了严格的区分:

C语言中的纯右值,比如:a+b, 100
将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

三、引用与右值引用比较

在C++98中的普通引用与const引用在引用实体上的区别:

int main()
{// 普通类型引用只能引用左值,不能引用右值int a = 10;int& ra1 = a; // ra为a的别名//int& ra2 = 10; // 编译失败,因为10是右值const int& ra3 = 10;const int& ra4 = a;return 0;
}

注意: 普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值。
C++11中右值引用:只能引用右值,一般情况不能直接引用左值。

int main()
{// 10纯右值,本来只是一个符号,没有具体的空间,// 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量int&& r1 = 10;r1 = 100;int a = 10;int&& r2 = a; // 编译失败:右值引用不能引用左值return 0;
}

四、值的形式返回对象的缺陷


如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错,比如:

class String
{
public:String(char* str = ""){if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}String(const String& s): _str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}String& operator=(const String& s){if (this != &s){char* pTemp = new char[strlen(s._str) + 1];strcpy(pTemp, s._str);delete[] _str;_str = pTemp;}return *this;}String operator+(const String& s){char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];strcpy(pTemp, _str);strcpy(pTemp + strlen(_str), s._str);String strRet(pTemp);return strRet;}~String(){if (_str) delete[] _str;}
private:char* _str;
};
int main()
{String s1("hello");String s2("world");String s3(s1 + s2);return 0;
}

上述代码看起来没有什么问题,但是有一个不太尽人意的地方:

在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁了,最后使用返回的临时对象构造s3,s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完
全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大
,那能否对该种情况进行优化呢?

五、移动语义

C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。

在C++11中如果需要实现移动语义,必须使用右值引用。上述String类增加移动构造:

String(String&& s): _str(s._str)
{s._str = nullptr;
}

因为strRet对象的生命周期在创建好临时对象后就结束了,即将亡值,C++11认为其为右值,在用strRet构造临时对象时,就会采用移动构造,即将strRet中资源转移到临时对象中。而临时对象也是右值,因此在用临时对象构造s3时,也采用移动构造,将临时对象中资源转移到s3中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。

注意:

1. 移动构造函数的参数千万不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
2. 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。

六、右值引用引用左值

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}

注意:

1. 被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量lvalue不会被销毁。
2. STL中也有另一个move函数,就是将一个范围中的元素搬移到另一个位置。

int main()
{
String s1("hello world");
String s2(move(s1));
String s3(s2);
return 0;
}

注意:以上代码是move函数的经典的误用,因为move将s1转化为右值后,在实现s2的拷贝时就会使用移动构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符串。


使用move的一个例子:

class Person
{
public:Person(char* name, char* sex, int age): _name(name), _sex(sex), _age(age){}Person(const Person& p): _name(p._name), _sex(p._sex), _age(p._age){}
#if 0Person(Person&& p): _name(p._name), _sex(p._sex), _age(p._age){}
#elsePerson(Person&& p): _name(move(p._name)), _sex(move(p._sex)), _age(p._age){}
#endif
private:String _name;String _sex;int _age;
};
Person GetTempPerson()
{Person p("prety", "male", 18);return p;
}
int main()
{Person p(GetTempPerson());return 0;
}

七、完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。

void Func(int x)
{// ......
}
template<typename T>
void PerfectForward(T t)
{Fun(t);
}

PerfectForward为转发的模板函数,Func为实际目标函数,但是上述转发还不算完美,完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,就好像转发者不存在一样。

所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。

C++11通过forward函数来实现完美转发, 比如:

void Fun(int& x) 
{ cout << "lvalue ref" << endl;
}
void Fun(int&& x)
{cout << "rvalue ref" << endl;
}
void Fun(const int& x)
{ cout << "const lvalue ref" << endl;
}
void Fun(const int&& x)
{ cout << "const rvalue ref" << endl;
}template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }
int main()
{PerfectForward(10); // rvalue refint a;PerfectForward(a); // lvalue refPerfectForward(std::move(a)); // rvalue refconst int b = 8;PerfectForward(b); // const lvalue refPerfectForward(std::move(b)); // const rvalue refreturn 0;
}

八、右值引用作用


C++98中引用作用:因为引用是一个别名,需要用指针操作的地方,可以使用指针来代替,可以提高代码的可读性以及安全性。

C++11中右值引用主要有以下作用:

1. 实现移动语义(移动构造与移动赋值)
2. 给中间临时变量取别名:

int main()
{
string s1("hello");
string s2(" world");
string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
stirng&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名
return 0;
}

3. 实现完美转发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/276502.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WX小程序案例(一):弹幕列表

WXML内容 <!--pages/formCase/formCase.wxml--> <!-- <text>pages/formCase/formCase.wxml</text> --> <view class"bk bkimg"><!-- <image src"/static/imgs/ceeb653ely1g9na2k0k6ug206o06oaa8.gif" mode"scal…

分享10个国内免费的AI绘画工具

谈到 AI 绘画&#xff0c;许多人会联想到 Midjourney、Stable Diffusion、DALLE2 等国外的知名绘画工具。 然而&#xff0c;这些国外的 AI 绘画工具大部分都是付费的&#xff0c;并且需要借助科学上网才能使用。这两个条件让许多人望而却步。 考虑到很多人无法进行科学上网&a…

【Pandas案例1】 根据某些相同属性列合并同类数据

文章目录 根据相同属性合并pandas行代码数据加载自定义方法主函数完整代码如下 根据相同属性合并pandas行 代码 提供的代码可直接运行 完整的逐步运行的ipynb代码项目化的py文件代码 以如下表格数据为例&#xff0c;针对t, i, j相同的行&#xff0c;对其后的v属性数据实现相加…

系统规划与管理师和信息系统项目管理师哪个好考?

软考系统规划与管理师和信息系统项目管理师是软考中备受关注的两个证书。这两个证书的相关知识领域广泛&#xff0c;对于从事IT行业的人们来说&#xff0c;都具有相当的吸引力。那么&#xff0c;对于考生而言&#xff0c;究竟哪个证书更适合呢&#xff1f;接下来&#xff0c;我…

Java 线程的基本概念

创建和运行线程 方法一&#xff0c;直接使用 Thread // 创建线程对象 Thread t new Thread() {public void run() {// 要执行的任务}};// 启动线程 t.start();例如&#xff1a; // 构造方法的参数是给线程指定名字&#xff0c;推荐 Thread t1 new Thread("t1") …

【深度学习】AlexNet网络实现猫狗分类

【深度学习】AlexNet网络实现猫狗分类 AlexNet简介 AlexNet是一种卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;模型&#xff0c;它在2012年的ImageNet图像分类挑战赛中取得了重大突破&#xff0c;引发了深度学习在计算机视觉领域的热潮…

Axure之动态面板轮播图

目录 一.介绍 二.好处 三.动态面板轮播图 四.动态面板多方式登录 五.ERP登录 六.ERP的左侧菜单栏 七.ERP的公告栏 今天就到这了哦&#xff01;&#xff01;&#xff01;希望能帮到你了哦&#xff01;&#xff01;&#xff01; 一.介绍 Axure中的动态面板是一个非常有用的组…

2023年四川网信人才技能大赛 决赛 实操赛Web ezbbs Writeup

题目是一个BSS论坛&#xff0c;如图 尝试注册发现注册未开放 题目给了jar包以及给了一个提示条件竞争绕过&#xff0c;分析源码&#xff1a; /register、/login接口都在com.my.bbs.controller.rest.BBSUserController 首先cacheUser是BBSUser类型的私有属性&#xff0c;并且reg…

LabVIEW开发远程结构健康监测系统

LabVIEW开发远程结构健康监测系统 工程师依赖于振动监测来评估建筑物、桥梁和其他大型结构的完整性。传统的振动监测工具在数据收集上存在限制&#xff0c;无法长时间收集高保真波形。随着内存存储、处理器速度和宽带无线通信技术的进步&#xff0c;出现了对能够长时间收集并实…

【NTN 卫星通信】Starlink,卫星互联网的技术革命(一)

1. 什么是Starlink Starlink是由Elon Musk创立的私人太空探索公司SpaceX提供的卫星互联网服务。它旨在为世界上传统互联网服务速度慢或不可用的偏远地区提供价格合理的高速互联网。 为什么Starlink很重要&#xff1f;   Starlink之所以重要&#xff0c;是因为它有可能为数百万…

深度解读:Spring Boot启动流程解析与应用实战

首先&#xff0c;让我们来探究一下Spring Boot的启动流程。Spring Boot是一个伟大的框架&#xff0c;它的设计目标之一就是简化开发过程&#xff0c;降低配置复杂性。它的启动过程也是异常精巧的&#xff0c;让我们逐步揭开这个神秘面纱。 应用入口类&#xff1a;SpringBootAp…

亚马逊云科技助力泡泡玛特快速部署全球弹性资源,打造国潮出海文化

企业全球化的终极目标就是品牌出海。1978年伴随着改革开放&#xff0c;中国企业开始放眼望世界输出中国产品&#xff0c;经过多年锤炼后&#xff0c;中国企业如TCL、泡泡玛特在不同的行业重塑版图&#xff0c;对外输出中国品牌&#xff0c;赢得了全球市场&#xff0c;中国企业实…