右值引用与移动语义

news2024/11/20 9:33:29

目录

一、左、右值引用

1.1 什么是左值

1.2 什么是右值

1.3 右值引用特性

1.4 move语义

二、左、右值引用的比较

三、右值引用的使用场景

3.1 左值引用的短板

3.2 解决方案 

四、移动构造与移动赋值

注意情况

五、万能引用与完美转发

5.1 万能引用

5.2 完美转发


一、左、右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以我们称之前学习的引用为左值引用。但无论左值引用还是右值引用,其实都是给对象取别名。

1.1 什么是左值

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址,也可以对它赋
值。左值可以出现赋值符号的左边,但右值不能出现在赋值符号左边。定义时const修饰符后的左
值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。(可以大致理解为,能够取地址的一般都为左值)

int main()
{
    //以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    
    //以下是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}

1.2 什么是右值

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(非左值引用返回)等等。右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

内置类型的右值引用被称为纯右值,自定义类型的右值引用被称为将亡值

int main()
{
    double x = 1.1, y = 2.2;
    //以下是常见的右值
    10;
    x + y;
    fmin(x, y);

    //以下是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);

    return 0;
}

1.3 右值引用特性

右值是不能取地址的,但给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。(即可以给右值引用取地址)
因为右值引用是左值

int main()
{
    //不能取字面量10的地址。
    //但是rr引用后,可以对rr取地址,也可以修改rr。
    int&& rr = 10;
    rr = 20;
    return 0;
}

1.4 move语义

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?

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

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
    // forward _Arg as movable
    return ((typename remove_reference<_Ty>::type&&)_Arg);
}
int main()
{
    bjy::string s1("hello world");
    
    bjy::string s2(s1);//这里s1是左值,调用的是拷贝构造
    
    bjy::string s3(std::move(s1));//将s1 move处理以后,会被当成右值,调用移动构造
    //一般是不这样用的,因为我们会发现s1的资源被转移给了s3,s1被置空了。
    return 0;
}

二、左、右值引用的比较

左值引用:

1. 左值引用只能引用左值,不能引用右值

2. const左值引用既可以引用左值,也可以引用右值

int main()
{
    //左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;//ra为a的别名
    //int& ra2 = 10;//编译失败,因为10是右值

    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;

    return 0;
}

右值引用:

1. 右值引用只能引用右值,不能引用左值

2. 右值引用可以move以后的左值

int main()
{
    // 右值引用只能右值,不能引用左值。
    int&& r1 = 10;
    
    int a = 10;
    int&& r2 = a;
    // error C2440: “初始化”: 无法从“int”转换为“int &&”
    // message : 无法将左值绑定到右值引用

    // 右值引用可以引用move以后的左值
    int&& r3 = std::move(a);

    return 0;
}

三、右值引用的使用场景

const左值引用既可以引用左值,也可以引用右值,那么为什么还需要右值引用呢?

 左值引用看似功能已经很完善了,但是在面对下面这些情况时,却捉襟见肘。

3.1 左值引用的短板

当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。

3.2 解决方案 

这个时候右值引用便可以解决这个问题了。

// 拷贝构造
string(const string& s)	:_str(nullptr), _size(0), _capacity(0)
{
	cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
	string tmp(s._str);
	swap(tmp);
}
// 移动构造
string(string&& s) :_str(nullptr), _size(0), _capacity(0)
{
	cout << "string(string&& s) -- 资源转移" << endl;
	swap(s);
}

利用右值引用提供了移动构造函数后,to_string函数中的str对象会被编译器识别为将亡值。之后需要发生构造时,则会自动调用移动构造函数。移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造。就是窃取别人的资源来构造自己,移动构造中没有新开空间,拷贝数据,所以比拷贝构造更加高效。

与移动构造类似的还有移动赋值,也是通过右值引用来提高效率。

// 拷贝赋值
string& operator=(const string& s)
{
	cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
	string tmp(s);
	swap(tmp);

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

	return *this;
}

 

四、移动构造与移动赋值

那么移动构造和移动赋值有什么需要注意的地方吗?

在C++98时,我们学习过C++的类中一共有6个默认成员函数(分别是构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const取地址重载)。但随着C++11的更新又新增了两个默认成员函数,即移动构造函数和移动赋值重载

注意情况

1. 若没有自主实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员则需要看这个成员是否存在移动构造,若存在就调用移动构造,不存在就调用拷贝构造。

2. 若没有自主实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员则需要看这个成员是否存在移动赋值,若存在就调用移动赋值,不存在就调用拷贝赋值。

3. 若提供了移动构造或者移动赋值中任意一个,编译器不会自动提供拷贝构造和拷贝赋值。

五、万能引用与完美转发

5.1 万能引用

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型就会被限制,在后续使用中都退化成了左值。所以万能引用也被称为引用折叠(即左值引用和右值引用都被折叠为左值)。
也可以换一种理解方式。在前面提到过右值引用的特性,右值引用是左值,且左值引用也是左值。所以不出意外,既能接收左值也能接收右值的万能引用也是左值。

#include <iostream>
using namespace std;

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<typename T>
void PerfectForward(T&& t)
{
    Fun(t);
}
int main()
{
    PerfectForward(10); //左值引用
    int a;
    PerfectForward(a); //左值引用
    PerfectForward(std::move(a)); //左值引用
    const int b = 8;
    PerfectForward(b); //const 左值引用
    PerfectForward(std::move(b)); //const 左值引用
    return 0;
}

如果希望能够在传递过程中保持它的左值或者右值的属性, 就需要用到完美转发

5.2 完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性

#include <iostream>
using namespace std;

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<typename T>
void PerfectForward(T&& t)
{
   Fun(std::forward<T>(t));
}
int main()
{
    PerfectForward(10); //右值引用
    int a;
    PerfectForward(a); //左值引用
    PerfectForward(std::move(a)); //右值引用
    const int b = 8;
    PerfectForward(b); //const 左值引用
    PerfectForward(std::move(b)); //const 右值引用
    return 0;
}

使用场景

在实际开发中,某些接口函数是提供了右值引用版本的,譬如STL中vector、list等容器的插入接口。传入右值参数并被右值引用接收后,会被认为是左值,无法顺利调用到移动构造和移动赋值等函数(没有真正减少拷贝、提高效率),这时就需要使用完美转发来在传参过程中保证右值对象的属性。

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

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

相关文章

【SpringMVC】SpringMVC中异常处理

1.异常处理的思路 系统中异常包括两类&#xff1a;预期异常和运行时异常 RuntimeException&#xff0c;前者通过捕获异常从而获取异常信息&#xff0c;后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。 系统的 dao、service、controller 都通过 throws Excepti…

以前的互联网技术几乎是解决一切痛点和难题的万能解药

事实上&#xff0c;以往&#xff0c;我们所经历的那个互联网玩家频出的年代&#xff0c;其实就是一个以互联网技术为主导的年代。在那样一个年代里&#xff0c;互联网技术几乎是解决一切痛点和难题的万能解药&#xff0c;几乎是破解一切行业痛点和难题的杀手锏。任何一个行业&a…

怎么恢复删除的照片?分享5种恢复 iPhone /iPad 照片的方法

从 iPhone 中删除照片可能会带来压力。以下是如何使用几种不同的方法从 iPhone 恢复最近删除的照片。运气好的话&#xff0c;如果照片没有被新数据覆盖&#xff0c;您可以取回照片。 方法 1. 使用iPhone照片恢复工具 以下是如何使用图像恢复软件从 iPhone 中恢复已删除的图片&a…

字节跳动模型大规模部署实战

动手点关注干货不迷路1. 背景介绍在字节跳动&#xff0c;基于深度学习的应用遍地开花&#xff0c;工程师关注模型效果的同时也需要关注线上服务一致性和性能&#xff0c;早期这通常需要算法专家和工程专家分工合作并紧密配合来完成&#xff0c;这种模式存在比较高的 diff 排查验…

电磁兼容教程

------------------------20221228--------------------------------- 定义&#xff1a; 电磁环境&#xff0c;设备正常工作&#xff0c;不干扰其他设备。共存 电磁兼容研究 有限时间、空间、频谱设备共存的科学 要素&#xff1a; 电磁环境是由空间、时间、频谱三要素组成。…

怎么把word转化为PDF?赶快试试这个方法!

怎么把word转化为PDF&#xff1f;我们都经常会编辑文件、记录数据&#xff0c;所以也经常会需要转换文件的格式&#xff0c;word文件转换为PDF文件是最热的转换操作之一&#xff0c;所以有很多人都在问小编到底该怎样进行转换&#xff0c;不知道怎样转换出来的文件质量高&#…

18-剑指 Offer 20. 表示数值的字符串

题目 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格一个 小数 或者 整数&#xff08;可选&#xff09;一个 e 或 E &#xff0c;后面跟着一个 整数若干…

Debian系列-开机启动程序

Debian系列-开机启动程序 文章目录Debian系列-开机启动程序摘要1 修改/etc/profile2 输入密码&#xff0c;以管理员权限运行程序关键字&#xff1a; 开机启动、 Debian、 Linux、 profile、 etc内容背景&#xff1a; 最近项目终于切到Linux下开发了&#xff0c;所以最近的记录…

sklearn.neighbors 最近邻相关算法,最近邻分类和回归

文章目录sklearn.neighbors 最近邻相关算法&#xff0c;分类和插值1. 查找最近邻元素2. 最近邻分类3. 最近邻回归4. NearestCentroid 最近邻质心分类5. Neighborhood Components Analysis 邻域成分分析sklearn.neighbors 最近邻相关算法&#xff0c;分类和插值 主要介绍 sklea…

day31【代码随想录】回溯之子集||、递增子序列、全排列、全排列||

文章目录前言一、子集 II&#xff08;力扣90&#xff09;二、递增子序列&#xff08;力扣491&#xff09;三、全排列&#xff08;力扣46&#xff09;四、全排列||&#xff08;力扣47&#xff09;总结前言 1、子集|| 2、递增子序列 3、全排列 4、全排列|| 一、子集 II&#xff…

【C++】指针的基础知识 | 学习笔记

文章目录前言一、指针的定义和使用1.1、指针定义1.2、指针使用二、指针占用的内存空间三、空指针和野指针3.1.空指针3.2 野指针四、const修饰指针4.1 常量指针4.2 指针常量4.3 const既修饰指针也修饰常量五、指针&#xff0c;数组&#xff0c;函数混用案例5.1 指针和数组混用5.…

Talk预告 | 上海交通大学计算机系博士生李杰锋方浩树:多人场景,全身136关键点检测与跟踪框架AlphaPose技术讲解

本期为TechBeat人工智能社区第466期线上Talk&#xff01; 北京时间12月28日(周三)20:00&#xff0c;上海交通大学计算机系博士生——李杰锋&方浩树的Talk将准时在TechBeat人工智能社区开播&#xff01; 他们与大家分享的主题是: “多人场景&#xff0c;全身136关键点检测与…

初识Unity

视频教程&#xff1a;史上最全Unity3D教程 常用快捷键 1.按住鼠标滚轮&#xff0c;拖动场景 2.滑动鼠标滚轮&#xff0c;缩放场景 3.右键&#xff0c;旋转视角 4.右键W、A、S、D&#xff0c;漫游视角&#xff0c;同时按下Shift可加速移动 5.alt鼠标左键&#xff0c;环视…

【财务】FMS财务管理系统---费用管理

在FMS财务管理系统中&#xff0c;和公司主营业务收入相关的费用有哪些&#xff1f;本篇文章中&#xff0c;笔者对具体分类和流程进行了系统的分析和总结&#xff0c;与大家分享。 财务中的费用管理主要包括销售费用、财务费用、管理费用等几大部分&#xff0c;看到费用大家首先…

C#,图像二值化(06)——全局阈值的大津OTSU算法及其源代码

1、大津OTSU算法 最大类间方差法是1979年由日本学者大津(Nobuyuki Otsu)提出的&#xff0c;是一种自适应阈值确定的方法&#xff0c;又叫大津法&#xff0c;简称OTSU&#xff0c;是一种基于全局的二值化算法&#xff0c;它是根据图像的灰度特性,将图像分为前景和背景两个部分。…

Git简介以及安装

目录 一、Git简介 1、版本控制系统简介 2、 Git的安装 a、安装git b、Git 的配置 二&#xff0c;本地仓库 三、GIT分支操作 1、关于分支 2. 分支基本操作 3、分支合并 4、冲突 一、Git简介 1、版本控制系统简介 版本控制系统&#xff08;VCS&#xff09;是将『什么…

【数据结构】直接插入排序,希尔排序,选择排序,堆排序

文章目录排序的概念直接插入排序希尔排序选择排序堆排序排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在…

keras环境搭建

目录 1. 安装miniconda 2. 安装CPU版本的tensorflow 2. 安装keras 3. 安装依赖库 4. 测试 环境&#xff1a;win10&#xff0c;无独立显卡&#xff0c;不用GPU加速。 1. 安装miniconda Miniconda3-latest-Windows-x86_64.exe &#xff08;1&#xff09;安装目录可自选&a…

生成对抗:Pix2Pix

cGAN : Pix2Pix 生成对抗网络还有一个有趣的应用就是&#xff0c;图像到图像的翻译。例如:草图到照片&#xff0c;黑白图像到RGB&#xff0c;谷歌地图到卫星视图&#xff0c;等等。Pix2Pix就是实现图像转换的生成对抗模型&#xff0c;但是Pix2Pix中的对抗网络又不同于普通的GAN…

计网第三章.数据链路层—可靠传输

以下来自湖科大计算机网络公开课的笔记 文章目录0.基本概念1. 停止等待协议SW2. 回退N帧协议GBN3. 选择重传SR首先&#xff0c;这部分说的可靠传输的实现机制不只限于数据链路层&#xff0c;而是适用于整个计算机网络体系 0.基本概念 一般情况下&#xff0c;有线链路的误码率…