C++11 右值引用和移动语义

目录

1.左值引用和右值引用

2.右值引用使用场景(移动语义)和意义

3.右值引用引用左值及其一些更深入的使用场景分析

4.完美转发 


1.左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时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;
}

什么是右值?什么是右值引用?

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

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);// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}

左值能否给右值取别名?---不可以,但是const左值引用可以。

​const int& rr1l = 10;const double& rr2l= x + y;const double& rr3l = fmin(x, y); ​

右值引用能否给左值取别名---不可以,但是可以给move以后的左值取别名。

	int* p = new int(0);int*&& rp = move(p);


2.右值引用使用场景(移动语义)和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

左值引用的使用场景:做参数和做返回值都可以提高效率。
 

左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:mystring::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

namespace mystring 
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){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);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造// 左值string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;return *this;}~string(){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];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)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};mystring::string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}mystring::string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}

        只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

int main()
{mystring::string ret1 = mystring::to_string(1234);cout << ret1.c_str() << endl;return 0;
}

两次拷贝构造,效率低。

右值引用和移动语义解决上述问题:
在mystring::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

下面的就是移动构造,与拷贝构造构成函数重载。

		// 移动构造// 右值(将亡值)string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}

        to_string 的返回值是一个临时对象(将亡值)也就是右值,用这个右值构造ret2,如果既有拷贝构造又有移动构造,调用就会匹配调用移动构造,因为编译器会选择最匹配的参数调用。那么这里就是一个移动语义。

       在编译器优化的情况下,to_string函数的返回值,会被编译器强制move变成右值,同样的调用移动构造,两次移动构造,编译器会再次优化,合成一次移动构造,直接使用str构造ret1,这里就不存在拷贝了,直接将右值的资源转移过来,提高了效率。

不仅仅有移动构造,还有移动赋值:
在mystring::string类中增加移动赋值函数,再去调用mystring::to_string(1234),不过这次是将mystring::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。

		string& operator=(string&& s){cout << "string(string&& s) -- 移动赋值" << endl;swap(s);return *this;}int main(){mystring::string ret1;ret1 = bit::to_string(1234);return 0;}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义

这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。mystring::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为mystring::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。


        我们在这里思考一个问题,在移动构造中,s是右值,我们在调用swap的时候,还是可以获取s的资源,但右值是不用可以被修改的?

        实际上,右值引用的属性本身是左值:一旦一个右值被绑定到一个右值引用上,这个右值引用本身就是一个左值,因为它有一个明确的存储位置。这样的意义,是为了移动构造移动赋值,转移资源的语法逻辑自洽。只有右值引用本身处理成左值,才能实现移动构造和移动赋值,转移资源。

3.右值引用引用左值及其一些更深入的使用场景分析

C++11引入了右值引用和移动语义,以支持更加高效的资源管理和对象操作。

右值引用

右值引用是C++11引入的一种新引用类型,其语法形式为T&&,其中T是某个类型。右值引用主要用于绑定到右值(如临时对象、字面量等),从而允许程序员对这些右值进行直接操作,避免不必要的拷贝。

移动语义

移动语义允许一个对象将其资源“移动”到另一个对象,而不是进行传统的拷贝。这样做的好处是避免了不必要的资源分配和析构,从而提高了程序的性能。为了实现移动语义,C++11中引入了std::move函数模板和移动构造函数以及移动赋值运算符。

int main()
{string s1("111111111111");string s3 = move(s1);
}

资源转移,而非简单的拷贝。

std::move

std::move是一个函数模板,它将其参数转换为右值引用。但是,std::move并不真正移动任何数据,它只是简单地执行一个类型转换。实际上,数据的移动(如果发生)是由移动构造函数或移动赋值运算符来完成的。

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

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

STL容器插入接口函数也增加了右值引用版本:

void push_back(value_type&& val);
int main()
{list<mystring::string> lt;mystring::string s1("1111");// 这里调用的是拷贝构造lt.push_back(s1);// 下面调用都是移动构造lt.push_back("2222");lt.push_back(std::move(s1));return 0;
}
运行结果:
// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义

        上面所作的效率提升,针对的是自定义类型的深拷贝的类,深拷贝的类才有转移资源的移动系列的函数。像日期类(浅拷贝自定义类型),内置类型不存在资源转移一说


4.完美转发 

        模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值.模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发

eg:接收的类型都退化成了左值

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

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

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

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

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

相关文章

Verilog:【8】基于FPGA实现SD NAND FLASH的SPI协议读写

在此介绍的是使用FPGA实现SD NAND FLASH的读写操作&#xff0c;以雷龙发展提供的CS创世SD NAND FLASH样品为例&#xff0c;分别讲解电路连接、读写时序与仿真和实验结果。 目录 1 视频讲解 2 SD NAND FLASH背景介绍 3 样品申请 4 电路结构与接口协议 4.1 SD NAND 4.2 SD NAND测…

机器学习算法的电影推荐系统以及票房预测系统

一、实验概述 1. 实验目标 本项目希望基于电影数据集&#xff0c;依据电影的简介、关键词、预算、票房、用户评分等特征来对电影进行分析&#xff0c;并完成以下任务&#xff1a; 对电影特征的可视化分析对电影票房的预测多功能个性化的电影推荐算法 2. 数据集 针对票房预…

AIGC-CVPR2024best paper-Rich Human Feedback for Text-to-Image Generation-论文精读

Rich Human Feedback for Text-to-Image Generation斩获CVPR2024最佳论文&#xff01;受大模型中的RLHF技术启发&#xff0c;团队用人类反馈来改进Stable Diffusion等文生图模型。这项研究来自UCSD、谷歌等。 在本文中&#xff0c;作者通过标记不可信或与文本不对齐的图像区域&…

vulnhub靶场之FunBox-11

一.环境搭建 1.靶场描述 As always, its a very easy box for beginners. Add to your /etc/hosts: funbox11 This works better with VirtualBox rather than VMware. 2.靶场下载 https://www.vulnhub.com/entry/funbox-scriptkiddie,725/ 3.靶场启动 二.信息收集 1.寻找靶…

通过腾讯云TDSQL TCPTCE(MySQL版)认证考试秘籍宝典

腾讯云TDSQL(MySQL版)交付运维高级工程师TCCP证书展示 腾讯云TDSQL(MySQL版)交付运维专家TCCE考试成绩、证书展示 认证类型与级别 TCCA:入门级(初级) TCCP:高级(中级) TCCE:专家级(高级) 考试形式 考试是在线考试&#xff0c;考生需要在腾讯云大学官网上完成。 腾讯云TDSQ…

LabVIEW项目中的常见电机及其特点分析

在LabVIEW项目中&#xff0c;电机的选择对系统的性能和应用效果至关重要。常见电机类型包括直流电机&#xff08;DC Motor&#xff09;、步进电机&#xff08;Stepper Motor&#xff09;、交流感应电机&#xff08;AC Induction Motor&#xff09;和无刷直流电机&#xff08;BL…

mongosh常用命令详解及如何开启MongoDB身份验证

目录 Mongosh常用命令介绍 连接到MongoDB实例 基本命令 查看当前数据库 切换数据库 查看所有数据库 查看当前数据库中的集合 CRUD操作 插入文档 查询文档 更新文档 删除文档 替换文档 索引操作 创建索引 查看索引 删除索引 聚合操作 数据库管理 创建用户 …

手把手教你软著申请(带视频+包括所有模板附赠软著申请软件)

基于前面的这个软件&#xff0c;这一次我沉淀两日重新归来&#xff01; 小唐读取软件全新升级&#xff01; 现在我们开始把我们软著申请流程重新走一遍&#xff01; 要不&#xff1f;你也来申请一张软著&#xff1f; 1.中国版权保护中心注册 1.1注册 大家在这个网址处写好自…

RedisConnectionException: Unable to connect to localhost/<unresolved>:6379

方法一&#xff1a;删除配置密码选项 一般是因为你在启动redsi服务的时候没有以指定配置文件启动 把application.yml文件中的redis密码注释掉 方法二 以指定配置文件启动 这样就不用删除yml文件中密码的选项了 在redis,windows.conf 中找到requirepass&#xff0c;删除掉前…

【JavaSE复习】基础、面向对象

JavaSE复习 1.Java入门1.1 cmd常见命令1.2 JDK下载和安装1.3 JRE和JDK 2.基础语法2.1 注释和关键字2.2 常量2.3 变量2.4 数据类型2.4.1 基本数据类型2.4.2 引用数据类型 2.5 IDEA 的下载和安装 3. 运算符3.1 算数运算符3.2 数据类型转换3.2.1 隐式转换3.2.2 强制转换 3.3 自增自…

【Matlab】CNN-LSTM分类 卷积神经网络-长短期记忆神经网络组合模型(附代码)

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/89466499 分类算法资源合集&#xff1a;https://download.csdn.net/download/vvoennvv/89466519 目录 Matlab SVM支持向量机分类算法 Matlab RF随机森林分类算法 Matlab RBF径向基神经网络分类算法 Ma…

服装连锁实体店bC一体化运营方案

一、引言 随着互联网的快速发展和消费者购物习惯的变化&#xff0c;传统服装连锁实体店在面对新的市场环境下亟需转型升级。BC&#xff08;Business to Consumer&#xff09;一体化运营方案的实施将成为提升服装连锁实体店竞争力和顾客体验的关键举掖。商淘云详细介绍服装连锁…

如何将现有系统逐步优化成微服务设计

目录 基础服务改造核心步骤准备阶段实施阶段 基础服务设计 本文诞生于学习架构实践专栏后的深思以及总结&#xff0c;结合公司之前“大泥球”的架构风格&#xff0c;改造服务设计的思维。 改造公司系统服务主要原因&#xff1a;1、代码类似“屎山”&#xff0c;牵一发而动全身&…

人工智能--搭建人工神经网络

欢迎来到 Papicatch的博客 文章目录 &#x1f349;引言 &#x1f349;神经元与感知器 &#x1f348;神经元&#xff08;Neuron&#xff09; &#x1f348;感知器 &#x1f349;损失函数与梯度下降算法 &#x1f348;损失函数 &#x1f348;梯度下降算法 &#x1f349;…

cd 命令特殊路径符 mkdir命令

cd 特殊路径符 cd . 表示当前目录&#xff0c;比如 cd ./Desktop表示切换到当前目录下的Desktop目录内&#xff0c;和 cd Desktop效果一致。cd … 表示上一级目录&#xff0c;比如 cd … 即可切换到上一级目录&#xff0c;cd…/…切换到上二级目录。cd ~ 表示 HOME 目录&#…

快速又不失灵活性的JeecgBoot框架

简介JeecgBoot 开源界 "小普元" 超越传统商业平台。引领低代码开发模式 (OnlineCoding-> 代码生成器 -> 手工 MERGE)&#xff0c;低代码开发同时又支持灵活编码&#xff0c; 可以帮助解决 Java 项目 70% 的重复工作&#xff0c;让开发更多关注业务。既能快速提…

面试官:JavaScript执行机制中的闭包?

前言 JavaScript 中的闭包指的是一个函数以及其捆绑的周边环境状态的引用的组合。闭包可以让开发者从内部函数访问外部函数的作用域&#xff0c;即使外部函数已经执行完毕 今天我们通过JavaScript执行机制来聊聊闭包 正文 首先来分析这段代码的执行机制&#xff0c;这段代码…

详细解读“找不到mfc140u.dll无法继续执行代码”问题

当你打开某个软件或者运行游戏&#xff0c;系统提示mfc140u.dll丢失&#xff0c;此时这个软件或者游戏根本无法运行。其实&#xff0c;mfc140u.dll是动态库文件&#xff0c;它是VS2010编译的软件所产生的&#xff0c;如果电脑运行程序时提示缺少mfc140u.dll文件&#xff0c;程序…

复分析——第6章—— Γ 函数和 ζ 函数(E.M. Stein R. Shakarchi)

第6章 Γ函数和Ζ函数(The Gamma and Zeta Functions) 毫不夸张地说&#xff0c;Γ函数和Ζ函数是数学中最重要的非初等函数之一。Γ函数在自然界中无处不在。它出现在大量计算中&#xff0c;并以分析中出现的大量恒等式为特征。对此的部分解释可能在于Γ函数的基本结构特性&…

python20 函数的定及调用

函数的定及调用 函数是将一段实现功能的完整代码&#xff0c;使用函数名称进行封装&#xff0c;通过函数名称进行调用。以此达到一次编写&#xff0c;多次调用的目的 用 def 关键字来声明 函数 格式&#xff1a; def 函数名(参数列表):函数体[:return 返回值是可选的&#xff0…