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

在这里插入图片描述
各位大佬好,我是落羽!一个坚持学习进步的学生。
如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步!

也欢迎关注我的blog主页: 落羽的落羽

文章目录

  • 一、C++11简介
  • 二、左值和右值是什么
  • 三、左值引用与右值引用
  • 四、移动构造和移动赋值
  • 五、引用折叠与完美转发

一、C++11简介

C++的发展历史上,有许多版本,比如C++98、C++11、C++14,不断更新新的语法。其中,继C++98后,C++11是一个相当重要的版本,更新了许多全新的语法,如右值引用、lambda表达式、function、bind等等,需要我们学习。

今天我们首先来学习C++11的右值引用和移动语义。

二、左值和右值是什么

左值和右值是现代C++中十分重要的概念。

在最早的定义中,判断左值右值有一个非常直观的方法:

  • 左值 (lvalue):可以出现在赋值语句等号 (=) 左侧的表达式。它代表一个在内存中有持久存储空间、有名字的、可以取地址的对象
    例如:变量、函数返回的引用、解引用的指针等
int a = 10; // a是左值
const int b = 1; // b是左值
std::string s; 
s = "hello"; // s是左值
s[1] // s[1]是左值
  • 右值 (rvalue):只能出现在赋值语句等号 (=) 右侧的表达式。它代表一个临时的、短暂的、没有名字的、无法取地址的值
    例如:字面量、函数返回的非引用类型、临时对象、表达式计算结果等。
int b = 20; // 20是右值
a + b; // a + b 这个表达式的结果是一个临时值,是右值
std::string("xyz") // 临时对象(匿名对象),是右值

在这里插入图片描述

  • 更深入的理解(C++11及以后)
    随着C++11引入移动语义,原来的“左右值”划分变得不够精细,于是引入了更复杂的值类别 (Value Categories)。所有表达式都属于以下三种主类别之一:左值 (lvalue)、将亡值 (xvalue)、纯右值 (prvalue)。后两者统称为右值 (rvalue)。这个不用过于关注,我们主要理解左值与右值。

左值与右值的核心区别就在于能否取地址

三、左值引用与右值引用

之前我们学习的引用,是左值引用。C++11引入了右值引用,用&&符号声明。

Type& r1 = x; // 左值引用
Type&& r2 = y; // 右值引用
  • 左值引用是给左值取别名,右值就是给右值取别名。

  • 左值引用不能直接引用右值,但是const左值引用可以引用右值。

  • 右值引用也不能直接引用左值,但是右值引用可以引用move(左值)move是库里的一个函数模板,能将一个左值转换为右值,其内部本质是强制类型转换。
    在这里插入图片描述

  • 需要额外注意的是,变量表达式都是左值属性的,也就是说一个右值引用变量表达式虽然引用的是右值,但他也是左值。如Type&& r2 = y;,y是右值,而r2是左值。

在函数传参这方面,我们之前习惯写为const左值引用的形式,因为它既能引用左值,也能引用右值。而有了右值引用后,我们就可以分别重载左值引用、const左值引用、右值引用的参数,会各自匹配对应的重载:
在这里插入图片描述

四、移动构造和移动赋值

移动构造函数和移动赋值重载是类的新的默认成员函数。

移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的右值引用,如果还有其他参数,其他参数必须有缺省值。
移动赋值重载是一个赋值运算符的重载,和拷贝赋值构成重载,要求第一个参数是该类类型的右值引用。

移动构造和移动拷贝的价值在于深拷贝的类或包含深拷贝的成员的类,如string、vector。因为移动构造和移动拷贝的第一个参数都是右值引用,他的本质是“窃取”引用的右值对象的资源,而不是像拷贝构造和移动赋值那样去拷贝资源,从而提高了效率。
这个点在于,“右值”的生命周期通常只有当前一行,生命周期很短,那么它的资源就可以不是拷贝走,而是直接移动走,因为它马上就要被销毁了,与其需要降低效率拷贝而且资源还要浪费,不如直接把它的资源移动给被拷贝对象,这样大大提升了效率。

举个栗子,我们自己写一个string类:

namespace lydly
{class string{private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;} iterator end(){return _str + _size;} const_iterator begin() const{return _str;} const_iterator end() const{return _str + _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)-构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷贝赋值" <<endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}} return* this;}// 移动赋值string & operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}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';}size_t size() const{return _size;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];if (_str){strcpy(tmp, _str);delete[] _str;} _str = tmp;_capacity = n;}}};
}

可以看出,拷贝构造中还需逐字符拷贝,但移动构造中直接swap就好,直接移动走右值参数的资源。

在这里插入图片描述
移动赋值同理,就不演示了。

移动构造和移动赋值的一个用处是函数返回深拷贝类型时,极大减少开销。举个例子:vector<int> v = createHugeVector(); (假设 createHugeVector 返回一个巨大的临时 vector),函数返回时需要创建一个临时对象,临时对象再构造出v。如果没有移动构造和移动赋值,这一过程需要有一次拷贝构造和拷贝赋值,就会导致巨大的内存开销;而C++11后STL容器有了移动构造和移动赋值时,构造临时对象和临时对象赋值就会直接“转移”资源而非拷贝,极大提高了效率。

C++11以后的容器,增加了移动构造和移动赋值,push和insert系列的接口也都增加了传右值的版本,当实参是右值时,容器内部调用移动构造进行拷贝,讲对象资源移动到容器空间的对象上,以vector为例:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

五、引用折叠与完美转发

C++中不能直接定义引用的引用, 如int& && r = i;会报错,但可以通过模板或tpyedef中的类型操作构成引用的引用。当构成引用的引用时,C++11有一个引用折叠的规则:右值引用的右值引用折叠成右值引用,其他所有组合均折叠成左值引用

typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; //r1的类型是int&
lref&& r2 = n; //r2的类型是int&
rref& r3 = n; //r3的类型是int&
rref&& r4 = n; //r4的类型是int&&

实例化模板时,如果折叠后参数类型是左值引用,传右值就会报错;折叠后参数是右值引用,传左值就会报错。
在这里插入图片描述

值得注意的是,形如以下的模板:

template<class T>
void Function(T&& t)
{
//...
}

传参为左值时,T推导为Type&,折叠后参数为Type&;传参为右值时,T推导为Tpye,不折叠,参数为Type&&。可以发现,模板参数为右值引用的模板,实例化后参数类型传左值就是左值引用,传右值就是右值引用,称之为万能引用。

但是我们之前讲到,变量表达式都是左值属性的,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说Function函数中不管t是左值引用还是右值引用的类型,t的属性都是左值,那么如果出现以下情形:

void Fun(int& x)
{cout << "左值" << endl;
}void Fun(int&& x)
{cout << "右值" << endl;
}template<class T>
void Function(T&& t)
{Fun(t);
}int main()
{Function(1);int a = 1;Function(a);return 0;
}

t的属性总是左值,因此只会调用到左值引用版本的Fun。
加粗样式

我们想要保持t对象的属性,就需要使用完美转发
在这里插入图片描述

完美转发forward本质是⼀个函数模板,他其实还是通过引用折叠的方式实现的。左值传给它时,返回左值;右值传给它时,还是返回右值。
在这里插入图片描述
使用完美转发必须显式传模板参数。所以,上述例子应该写成:

void Fun(int& x)
{cout << "左值" << endl;
}void Fun(int&& x)
{cout << "右值" << endl;
}template<class T>
void Function(T&& t)
{Fun(forward<T>(t));
}

在这里插入图片描述
本篇完,感谢阅读。

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

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

相关文章

Logic Error: 如何识别和修复逻辑错误

逻辑错误是指程序中的代码在语法上是正确的&#xff0c;但在执行时没有按预期工作。这种错误可能导致程序输出错误的结果或行为异常。逻辑错误通常比语法错误更难检测&#xff0c;因为它们不会产生编译或解释错误。本文将详细介绍如何识别和修复逻辑错误。一、识别逻辑错误1. 理…

TUN模式端口冲突 启动失败如何解决?

从日志信息来看&#xff0c;TUN模式启动失败是由于端口冲突导致的。 具体来说&#xff0c;Xray在尝试监听10808端口时失败了&#xff0c;因为该端口已经被其他进程占用。 错误信息分析 Failed to start: app/proxyman/inbound: failed to listen TCP on 10808 > transport/i…

如何调试一个EVM合约:实战操作 + 常见报错说明

在Solidity开发过程中&#xff0c;大多数开发者最常遇到的问题不是“代码写不了”&#xff0c;而是“代码部署了&#xff0c;但行为不对”。本篇文章将带你梳理一套完整的EVM智能合约调试流程&#xff0c;并附上几类真实常见报错场景及排查方法&#xff0c;适用于Hardhat、Remi…

使用Wireshark分析自助终端机网络数据

如果是明文还好&#xff0c; 是密文就没办法了。工具.1自助终端机.2组装结构主流架构选择‌B/S架构‌&#xff1a;通过Web应用调用本地硬件插件&#xff0c;开发速度快但依赖浏览器兼容性。 ‌‌C/S架构‌&#xff1a;直接调用硬件驱动&#xff0c;交互响应快但更新维护复杂。 …

数学建模——马尔科夫链(Markov Chain Model)

数学建模——马尔科夫链&#xff08;Markov Chain Model&#xff09;一、马尔可夫链的定义1. 状态与状态空间2. 无后效性&#xff08;马尔科夫性&#xff09;​3. 转移概率与转移概率矩阵&#xff08;1&#xff09;一步转移概率&#xff08;2&#xff09;转移概率矩阵二、马尔科…

《拉康精神分析学中的欲望辩证法:能指的拓扑学与主体的解构性重构》

在当代人文思想图谱中&#xff0c;雅克拉康以语言学为利刃对弗洛伊德理论进行的结构性重铸构成了20世纪最具颠覆性的理论创造之一。这位被誉为"法国弗洛伊德"的思想巨匠通过"回到弗洛伊德"的口号&#xff0c;实则完成了对精神分析学的哥白尼式革命——将主…

数字时代下的智能信息传播引擎

在商场、楼宇、交通枢纽等公共场所&#xff0c;数字广告机已成为信息传播的重要载体。其背后的广告机系统&#xff0c;是一套集硬件控制、内容管理、网络传输与数据分析于一体的综合技术解决方案&#xff0c;正推动传统静态广告向动态化、交互化、智能化方向演进。系统架构与核…

文献阅读笔记:KalmanNet-融合神经网络和卡尔曼滤波的部分已知动力学状态估计

文献阅读笔记&#xff1a;KalmanNet-融合神经网络和卡尔曼滤波的部分已知动力学状态估计摘要一、研究背景1.1 状态估计问题的重要性1.2 传统方法的局限&#xff1a;非线性与模型不确定性非线性问题噪声统计未知问题1.3 数据驱动方法的兴起与局限1.4 KalmanNet&#xff1a;混合方…

使用EasyExcel根据模板导出文件

文章目录概要工具类核心功能核心代码解析模板导出核心方法文件下载处理HTTP响应设置文件下载处理使用示例概要 在企业级应用开发中&#xff0c;Excel数据导出是一个常见的需求。本文实现一个基于阿里巴巴EasyExcel库实现的根据模板导出文件的工具类&#xff0c;它通过预定义的…

【AI基础:神经网络】19、机器学习实战:径向基函数神经网络(RBFN)指南

一、引言:为什么RBFN是神经网络中的“局部专家”? 在机器学习领域,神经网络的“全局逼近”与“局部逼近”一直是两大核心思路。像我们熟悉的多层感知机(MLP),使用Sigmoid、ReLU等全局激活函数,每个神经元都会对整个输入空间产生响应——就像“全员参与”处理所有数据,…

Linux 性能调优实战:CPU、磁盘 I/O、网络与内核参数

前言 一、CPU 资源调优 1. 调整进程优先级&#xff08;nice/renice&#xff09; 2. 设置 CPU 亲和力&#xff08;taskset&#xff09; 3. 查看 CPU 信息 4. 使用 vmstat 分析系统瓶颈 二、磁盘 I/O 调优 1. ulimit 资源限制 2. 磁盘速度测试 三、内核参数调优 1. 常用…

【进阶篇第五弹】《详解存储过程》从0掌握MySQL中的存储过程以及存储函数

文章目录存储过程一、基本语法(1)创建存储过程(2)调用存储过程(3)查看存储过程(4)删除存储过程(5)设置结束符(6)参数二、变量(1)系统变量(2)用户自定义变量(3)局部变量三、基本语句(1)if判断(2)case(3)while循环(4)repeat(5)loop循环四、游标五、条件处理程序六、存储函数存储过…

HarmonyOS布局实战:用声明式UI构建自适应电商卡片

首先诚邀大家参加学习鸿蒙拿好礼活动&#xff0c;即日起&#xff0c;只要加入班级考取华为开发者基础/高级证书&#xff0c;并发表一篇技术文章&#xff0c;就有机会获得官方发放的精美礼品&#xff0c;数量有限&#xff0c;先到先得。冷老师的班级链接如下&#xff1a;​华为开…

日语学习-日语知识点小记-构建基础-JLPT-N3阶段(21):文法+单词第7回3

日语学习-日语知识点小记-构建基础-JLPT-N3阶段&#xff08;&#xff12;1&#xff09;&#xff1a;文法单词第7回3 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰2、知识点&#xff11;ー 。。。と。。。なる&#xff12;ーVて欲しい ・ …

Baumer高防护相机如何通过Tiny-YOLO单类模型实现人体跌倒检测与跟踪(C#代码UI界面版)

《------往期经典推荐------》 AI应用软件开发实战专栏【链接】 序号项目名称项目名称11.工业相机 YOLOv8 实现人物检测识别&#xff1a;&#xff08;C#代码&#xff0c;UI界面版&#xff09;2.工业相机 YOLOv8 实现PCB的缺陷检测&#xff1a;&#xff08;C#代码&#xff0…

从源码看浏览器弹窗消息机制:SetDefaultView 的创建、消息转发与本地/在线页通用实践

引言在现代浏览器的开发中&#xff0c;前端页面和 C 内核之间的通信是一项核心功能。无论是本地设置页&#xff08;chrome:// 内置 H5&#xff09;还是在线活动页&#xff0c;前端都可能需要调用浏览器底层 API&#xff0c;实现诸如“设置默认浏览器”、“更改壁纸”、“读取用…

对比视频处理单元(VPU)、图形处理器(GPU)与中央处理器(CPU)

如今选择互联网点播流媒体与直播视频的用户数量已远超传统广播电视&#xff0c;这一转变催生了对高性能媒体转码与OTT流媒体功能专用技术的需求。 我们最新推出的Accelerated Compute云计算解决方案&#xff0c;首次通过NETINT Quadra视频处理单元&#xff08;VPU&#xff09;…

vue3写一个简单的时间轴组件

插件版本&#xff1a;"element-plus": "^2.3.12""vue": "^3.0.0"代码示例&#xff1a;样式文件style.less&#xff1a;改变el-tooltip样式&#xff0c;可以复制代码到公共样式文件.el-popper.o-el-tooltip-popper-class {max-width: 3…

Linex系统网络管理(二)

二、网络连接查看1. netstat作用查看本地服务的网络监听状态查看客户端连接到本地服务的连接状态语法&#xff1a;netstat 选项 &#xff08;-anptu&#xff09;选项作用-n&#xff0c; --numeric显示数字形式地址而不是去解析主机、端口或用户名-a, --all显示所有的监听或连接…

Unity MQTT通讯

首先明确概念&#xff0c;什么是MQTT&#xff1f; MQTT是一种轻量级、基于发布 / 订阅&#xff08;Publish/Subscribe&#xff09;模式的物联网&#xff08;IoT&#xff09;通信协议&#xff0c;在带宽有限、网络不稳定的环境下&#xff0c;实现低功耗、低延迟的设备间通信&am…