C++11 右值,右值引用,移动构造,移动赋值

news2025/3/2 3:17:16

目录

一、左值,左值引用,右值,右值引用的相关概念:

1. 什么是左值,什么是左值引用?

2. 什么是右值,什么是右值引用?

3. 右值的属性是右值,右值引用的属性是左值

4. 左值引用与右值引用的简单比较:

二、右值引用的作用(使用场景)

左值引用的作用:

左值引用的短板:

右值引用作用1,使用场景1:

to_string传值返回,string类的拷贝构造 vs 移动构造

to_string传值返回,string类的拷贝赋值 vs 移动赋值

场景一总结:

右值引用作用2,使用场景2:

三、模板中的万能引用&&  与  完美转发


一、左值,左值引用,右值,右值引用的相关概念:

1. 什么是左值,什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),左值的特征是可以获取它的地址 + 可以对它赋值,左值可以出现在赋值符号的左边。(右值不能出现在赋值符号的左边)。
有一个例外:const修饰的左值不能出现在赋值符号左边。
故,左值最大的特点是是:可以对它取地址。

左值引用就是对左值的引用,给左值取别名。

// 以下的p,b,c,*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
*p = 10;

// 左值引用:
int*& rp = p;
int& rb = b;
const int& rc = c;
int& ri = *p;

2. 什么是右值,什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回,也就是必须是值返回)... 
右值不能出现在赋值符号的左边,可以出现在赋值符号的右边,右值不能取地址。
右值引用就是对右值的引用,给右值取别名。
赋值运算符的左侧必须为左值。“=”: 左操作数必须为左值。

右值可以分为两种:1. 内置类型右值-纯右值    2. 自定义类型右值-将亡值

double x = 10.1, y = 20.2;
// 右值示例:


10;
x+y;
fmin(x, y);  // 值返回

// 右值引用示例:

int&& ri = 10;
double&& rd = x + y;
double&& rd2 = fmin(x, y);

3. 右值的属性是右值,右值引用的属性是左值

右值不能取地址,比如10不能取地址。但是右值引用可以取地址,且可以给右值引用赋值。也就是 int&& ri = 10; &ri; ri = 20;  是合法的。因为给右值取别名后,会导致右值引用被存储在特定位置,属性变为左值。

右值引用的属性是左值。const 右值引用的属性是const 左值。因为左值可以取地址,可以赋值。因此右值引用可以取地址,可以赋值。const 右值引用可以取地址,不能赋值。

4. 左值引用与右值引用的简单比较:

1. 左值引用只能引用左值,不能引用右值。 const 左值引用既能引用左值,也能引用右值。

2. 右值引用只能引用右值,不能引用左值。 但是右值引用可以引用move后的左值。(这个其实就是move函数的作用,std::move函数可以将左值强制转化为右值引用,返回这个左值的右值引用)

二、右值引用的作用(使用场景)

既然左值引用可以引用左值,且const 左值引用还可以引用右值,那么右值引用的作用是什么呢?探究C++11引入右值引用的作用之前,先来探究左值引用的作用,以及左值引用的短板,从而理解右值引用的作用。

左值引用的作用:

1. 左值引用做函数参数。

a. 引用传参,减少拷贝,提高效率。最典型的比如拷贝构造函数,operator=(),以对象为模板拷贝构造一个新的对象,这里传引用可以减少传参时的拷贝,提高效率。
b. 做输出型参数,代替C语言的传指针。

2. 左值引用做函数返回值。

a. 传引用做返回值,减少拷贝,提高效率。(我们知道,传值返回是要拷贝构造一个临时对象的(编译器不优化或不能优化的情况下),这里会降低效率。)
b. 传引用返回,用于修改返回对象。最典型的比如 vector的operator[]。很多堆区开辟的对象,都可以采用传引用返回的方式,使程序使用对一个对象进行处理。

重新审视拷贝构造函数和重载赋值运算符函数的参数,为什么要定为 const Type& t   一方面,传引用可以提高效率,减少拷贝。这里加const的作用可以防止修改左值引用。另一方面是,const 左值引用才能引用右值。否则这个拷贝构造函数或operator=不能以右值为实参。也就是 T t(T());  将报错,因为左值引用不能引用临时对象的这种右值。

左值引用的短板:

若一个对象在堆区,则函数可以传引用返回。但是,对于函数内的局部临时对象,函数返回后,出了作用域,局部对象就会销毁,是不可以传引用返回的。
而如果采用值返回,则会发生拷贝构造,若这个对象不是内置类型,而是深拷贝的自定义类型,则发生深拷贝还会降低效率。 对于自定义类型的右值,我们称之为将亡值,也就是函数内的局部临时对象。

右值引用作用1,使用场景1:

C++11中,右值引用的一个重要功能,就是解决函数值返回涉及深拷贝的局部临时对象的低效率问题。解决方法为:利用右值引用为自定义类型实现移动构造和移动赋值成员函数。

to_string传值返回,string类的拷贝构造 vs 移动构造

比如,我们模拟实现一个string类。则,yzl::string to_string(int val); 函数,用于将整型转为我们实现的string类型,此处必须传值返回,因为函数内创建的局部string对象出了函数作用域就会销毁。

yzl::string to_string(int val)
{
    yzl::string str;
    // ...
    return str;    // 此str的属性为右值
}

void func()
{
    yzl::string s = yzl::to_string(10);
}

// 以模拟实现string类为例的移动构造和移动赋值实现。

        // 移动构造
        string(string &&s)
                : _str(nullptr), _size(0), _capacity(0) {
            cout << "string(string&& s) -- 移动语义" << endl;
            swap(s);  
        }

        // 移动赋值
        string &operator=(string &&s) {
            cout << "string& operator=(string&& s) -- 移动语义" << endl;
            swap(s);
            return *this;
        }

若,我们不使用右值引用在yzl::string类内实现移动构造和移动赋值,只有拷贝构造和拷贝赋值。则这里在编译器未优化的情况下,要进行两次拷贝构造。若编译器对此情况进行优化,则进行一次拷贝构造。(to_string的str局部对象是右值属性,但是因为const &可以接收右值,所以调用拷贝构造)

若,yzl::string类或者其他深拷贝的自定义类型,实现了移动构造。这里的str作为局部对象值返回时,它的性质为右值,所以就会调用参数更匹配的移动构造。

若编译器不进行优化,则会调用两次移动构造函数,因为str 和 str构造出的临时对象都为右值。
若编译器进行优化,则只会调用一次移动构造函数。

注意:这里编译器的优化行为不是重点。

重点是,对于这种涉及深拷贝的局部对象的函数返回值,属性为右值,这种对象在函数执行结束,出了函数作用域后就会销毁,若没有移动构造或移动赋值。则就会进行深拷贝的拷贝构造。但是,对于这种将亡值,是没必要进行深拷贝的,我们可以直接将其资源进行转移。方式就是调用移动构造。


to_string传值返回,string类的拷贝赋值 vs 移动赋值

上方举例时,使用的是拷贝构造函数和移动构造函数,也就是函数值返回用于构造一个新的对象。

赋值的情况同理,若函数返回值用于赋值给一个同类对象,且这个类还没有实现移动赋值,则就会调用深拷贝的拷贝赋值,即string& operator=(const string& s); (注意,const左值引用可以引用右值)  对于这种局部对象的将亡值,没必要进行深拷贝赋值。可以实现 string& operator=(string && s);  移动赋值。即可减少深拷贝,提高效率。

yzl::string to_string(int val)
{
    yzl::string str;
    // ...
    return str;    // 此str的属性为右值
}

void func()
{
    // 赋值场景
    yzl::string s("hahaha");
    s = yzl::to_string(10);
}

暂不探究这里的编译器优化行为,若不进行优化。

若没有实现移动构造和移动赋值,则会先进行拷贝构造,然后拷贝赋值,都是以一个右值将亡值为参数,进行深拷贝,这显然是没必要的且低效的。若实现了移动构造和移动赋值,则这里会先进行移动构造,再进行移动赋值。

场景一总结:

右值引用的使用场景1,指的是当函数局部对象进行值返回时,不能采用左值引用返回,若不利用右值引用实现出移动构造和移动赋值,则对一个将亡值进行深拷贝效率低下。

这里并不是直接将返回值设为右值引用,而是利用右值引用实现出移动构造和移动赋值。减少对将亡值右值的深拷贝。

总结就是,传值返回深拷贝类型对象时,拷贝构造和移动构造可提高效率。

右值引用作用2,使用场景2:

STL中的vector,list等容器在C++11之后,都实现了右值引用版本的push_back,等插入函数。
函数调用时,若实参是一个右值对象,则插入函数内部会进行资源移动,减少拷贝(深拷贝)。若实参是一个左值对象,则插入函数内部会进行拷贝构造,拷贝赋值等深拷贝操作。

void haha2()
{
    list<yzl::string> ls;
    yzl::string s1("haha");
    ls.push_back(s1);
    
    cout << endl << endl;

    ls.push_back("hehe");
    ls.push_back(yzl::string("hehe"));
    ls.push_back(std::move(s1));
}

 对于不同属性的实参,调用形参类型最匹配的函数,对于右值,将亡值类型,调用移动构造或移动赋值等减少深拷贝,提高效率的类成员函数。

三、模板中的万能引用&&  与  完美转发

1. 类模板或函数模板中的&&,不代表右值引用,而是万能引用。
2. 其既能接收左值,也能接收右值(万能)
3. 模板的万能引用只是提供了同时能够接收左值引用和右值引用的能力
4. 不管万能引用接收的是左值还是右值,这个万能引用都是左值属性!(其实,左值引用本身就是左值属性,而上面说过右值引用也会是左值属性。)
5. 若我们希望在传递过程中保持原对象的左值或右值属性,就需要使用完美转发,
std::forward<T>(x); std::forward<T>(x)在传参的过程中保持了t的原生类型属性。

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; }

// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template <typename T>
void PerfectForward(T &&t) // 这里的万能引用,不是右值引用。普通的右值引用不能接收左值。
{
    Fun(t);
}

int test1()
{
    PerfectForward(10); // 右值
    
    int a;
    PerfectForward(a);            // 左值
    PerfectForward(std::move(a)); // 右值
    
    const int b = 8;
    PerfectForward(b);            // const 左值
    PerfectForward(std::move(b)); // const 右值
    return 0;
}

int main()
{
    test1();
    return 0;
}

 若在万能引用的传参过程中,使用std::forward<T>(t); 则会保持t的原生属性。

我们知道,STL中的容器,都是用模板实现的,里面在C++11之后,一些函数实现了右值引用版本,而STL容器具体实现是很复杂的,层层调用,里面很多地方都会使用完美转发。

若我们自己使用模板模拟实现STL容器,实现右值引用版本的成员函数时,也必须使用完美转发,具体就不举例了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

棒子老虎鸡-第12届蓝桥杯Scratch选拔赛真题精选

[导读]&#xff1a;超平老师计划推出Scratch蓝桥杯真题解析100讲&#xff0c;这是超平老师解读Scratch蓝桥真题系列的第86讲。 蓝桥杯选拔赛每一届都要举行4~5次&#xff0c;和省赛、国赛相比&#xff0c;题目要简单不少&#xff0c;再加上篇幅有限&#xff0c;因此我精挑细选…

研究生有限元仿真应用中存在的问题与对策

作者&#xff1a;尚晓江 导读&#xff1a;有限元分析软件作为计算工具&#xff0c;在科研和工程领域都有广泛应用&#xff0c;而多数用户是在研究生阶段开始接触和使用这些计算软件的。本文以ANSYS结构分析为例&#xff0c;对现阶段研究生应用有限元分析软件的现状和存在的问题…

无人机设计仿真--在Isight平台上进行的基于CST参数化+Xfoil的无人机翼型优化

作者&#xff1a;Graychen 一、工程背景 翼型的选型和设计是飞行器气动设计中的一项基础性工作&#xff0c;翼型对飞行器的气动性能具有根本性的影响。现在高性能飞行器已不再从翼型库中选择适用翼型后直接使用&#xff0c;而是以现有翼型作为基准翼型进行气动优化&#xff…

java基本语法 下

目录 运算符 运算符&#xff1a;算术运算符 运算符&#xff1a;赋值运算符 运算符&#xff1a;比较运算符 运算符&#xff1a;逻辑运算符 运算符&#xff1a;三元运算符 运算符的优先级 程序流程控制 概念 顺序结构 if-else结构 switch-case结构 循环结构 循环结构…

Unity视差贴图多实现对比和改进

视差贴图多种实现方式对比和改进视差贴图视差映射陡峭视差映射视差遮蔽映射迭代视差映射-kerry视差贴图 参考 与法线贴图相同&#xff0c;可以模拟出物体得深度感&#xff0c;同时它得改进是能够随着视角得偏移显示不同得深度感&#xff0c;使得显示更加真实。 由于采样高度…

代码随想录刷题| 多重背包理论基础、背包问题的总结

目录 多重背包理论基础 多重背包的问题 多重背包的解法 多重背包的代码 背包问题的总结 01背包 完全背包 多重背包 多重背包理论基础 多重背包的问题 有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用&#xff0c;每件耗费的空间是Ci &#xff0c;价值是Wi 。…

单身福利专场, Python采集某相亲网站美女数据

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 现在&#xff0c;广大年轻人到了一定年纪&#xff0c;一定会引来父母的念叨 不是让相亲就是让结婚的&#xff0c;与其父母念叨&#xff0c;不如自己找一个 到时候问起来&#xff0c;就说再接触呢~~ 今天我们就来用python…

NestJS学习:使用session实现登录验证

参考 大佬的视频教程&#xff1a;nestjs session案例 大佬的博客地址&#xff1a;小满nestjs&#xff08;第九章 nestjs Session&#xff09; 在学习某些知识时如果有大佬的视频教程与文档真的是太爽了&#xff0c;能够学习到好多新知识。 nest后台 session session 是服务…

目标检测算法——3D公共数据集汇总(附下载链接)

>>>深度学习Tricks&#xff0c;第一时间送达<<< &#x1f680;&#x1f680;&#x1f680;近期&#xff0c;小海带在空闲之余&#xff0c;收集整理了一批3D公共数据集供大家参考。 整理不易&#xff0c;小伙伴们记得一键三连喔&#xff01;&#xff01;&…

十个值得珍藏的正则表达式

正则表达式常学常忘&#xff0c;记规则不如记例子&#xff0c;记多不如记精&#xff0c;记例子就记最经典的。下面是本人珍藏的十个有用的正则表达式&#xff0c;不吝分享&#xff0c;以飨读者。 正则表达式要点 小括号&#xff1a;代表分组 中括号&#xff1a;代表集合 大括号…

回归分析(1)-回归分析的基本概念

1.回归方程 由于x是可控的非随机变量&#xff0c; 而Y 是一个与x有关的随机变量&#xff0c;因此&#xff0c;直接研究变量Y与x之间的相关关系是困难的&#xff0e; 如果注意到随机变量Y的数学期望反映了随机变量Y的平均取值&#xff0c;因此&#xff0c; 可考虑研究EY与x之间的…

第六章 支持向量机

6.1 间隔与支持向量 给定一个训练样本集&#xff0c;分类学习最基本的想法就是基于训练集D在样本空间中找到一个划分超平面。但是这个划分超平面也是很多的&#xff0c;如下图所示 直观上应该去找两类样本中最中间的划分超平面&#xff0c;因为该划分超平面对训练样本局部扰动…

与分类有关的一种时序优先现象

(A&#xff0c;B)---2*30*2---(1,0)(0,1) 用网络分类A和B&#xff0c;让A由两张图片组成&#xff08;0&#xff0c;0&#xff09;&#xff08;0&#xff0c;1&#xff09;&#xff0c;让B由两张图片组成&#xff08;1&#xff0c;0&#xff09;&#xff08;0&#xff0c;0&…

(附源码)计算机毕业设计JavaJava毕设项目租车网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven Vue 等等组成&#xff0c;B/…

Ubuntu22.04:设置Samba服务

Samba服务可以实现在主机间共享文件与Windows的共享通用。 Ubuntu22.04可以按照如下步骤安装并开启Samba服务&#xff1a; 1.安装samba sudo apt install samba 2.创建一个用于共享的目录(路径和路径名可以自行决定&#xff09; mkdir samba 3.设置共享目录的访问权限 s…

Prometheus的remotewrite for java

1、增加参数重启prometheus 修改 prometheus启动参数 - "--enable-featureremote-write-receiver" 重启 prometheus 2、下载案例 GitHub - bprasen/remotewrite 3、迁移 将案例中的代码复制到springboot/springcloud中 pom中增加 <!-- prometheus remote writ…

Windows OpenGL 图像阴影

目录 一.OpenGL 图像阴影 1.原始图片2.效果演示 二.OpenGL 图像阴影源码下载三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 特效 零基础 OpenGL ES 学习…

【机器学习项目实战10例】(一):利用线性回归实现股票预测分析

💥 项目专栏:【机器学习项目实战10例】 文章目录 一、利用线性回归实现股票预测分析二、加载股票数据三、将数据进行标准化四、形成训练数据五、加载数据集六、构建线性回归模型七、精度测试八、网格搜索九、绘制结果一、利用线性回归实现股票预测分析 股价预测其实是一个较…

PyTorch深度强化学习中蒙特卡洛策略梯度法在短走廊环境(CartPole-v0)中的实战(超详细 附源码)

需要源码请点赞关注收藏后评论区留下QQ~~~ 一、策略梯度法 策略梯度法&#xff08;PG&#xff09;利用策略函数来选择动作&#xff0c;同时使用值函数来辅助策略函数参数的更新&#xff0c;根据策略类型的不同&#xff0c;可以分为随机策略梯度和确定性策略梯度 策略梯度法与…

1990-2021年全国各省城市化水平数据

1990-2021年全国各省城市化水平数据 1、包括&#xff1a;全国31省 2、时间&#xff1a;1990-2021年 3、指标包括&#xff1a;城镇常住人口数(万人)、常住人口数(万人)、城市化水平 4、指标衡量&#xff1a;城市化率本地区城镇人口/总人口 5、指标解释&#xff1a; 城市化…