右值引用和移动语义

什么是左值?什么是右值?

通俗来讲,可以出现在赋值语句左侧的,为左值;只能出现在赋值语句右侧的,为右值。

左值与右值的本质区别在于:左值能取地址,但右值不能

本文主要通过三个场景 —— 与 移动构造、移动赋值、完美转发 有关,讲解右值引用在实际场景中的作用及其相关的知识。

补充: 右值通常也被形象的称为“将亡值”,代指赋值重载和拷贝构造过程中产生的临时对象

为了观察现象,我们要用到“自己搭的轮子”:

namespace MyTest
{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(const 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=(const string& s) -- 拷贝赋值" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}string& operator+=(char ch){push_back(ch);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';}const char* c_str() const{return _str;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做标识的\0};string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}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. 移动构造

1.1 原理讲解
int main()
{MyTest::string s1 = MyTest::to_string(1234);return 0;
}

要分别从 C++11 前后编译器优化前后 四个维度讨论 MyTest::string s1 = MyTest::to_string(1234); 的发生过程。

观察上图的右下角:很明显,str 是左值,在没有 move 的情况下,为什么可以直接移动构造

实际上,编译器已经做过优化了 —— 将传值返回函数的返回值隐式地 move 过了

1.2 移动构造实现
	// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}

2. 移动赋值

2.1 原理
int main()
{MyTest::string s1;s1 = MyTest::to_string(1234);return 0;
}

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

3. 完美转发

3.1 forward 及其用法

move(...) 表达式的结果为右值,但并不会改变 ... 本身的属性 。

forward<T>(x) 返回一个引用表达式,这个引用表达式的类型取决于 x 的原始类型和上下文。

​ 若 x 为右值,该引用表达式具有右值属性

​ 若 x 为左值,该引用表达式具有左值属性

  • 右值的右值引用具有左值属性
void func(const int& x)
{cout << "const int&" << endl;
} void func(int&& x)
{cout << "int&&" << endl;
}template<class T>
void fun(T&& x)
{func(x);// 此处 x 为右值还是左值呢?不妨运行该段代码,试试看
}int main()
{// int&& a = 10;// 10 为右值;a 为 10 的右值引用,因此具有左值属性fun(10);return 0;
}

做一处修改:

template<class T>
void fun(T&& x)
{func(forward<T>(x));// 再运行试试
}

3.2 模板中的 && 万能引用

请记住:万能引用 与 模板 紧密相关!

void func(int& x) { cout << "左值引用" << endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用" << endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }template<class T>
void Universal_citation(T&& x) // 万能引用
{ func(forward<T>(x));
}int main()
{int a;Universal_citation(a);Universal_citation(move(a));const int b = 10; // b 必须要初始化Universal_citation(b);Universal_citation(move(b));return 0;
}

3.3 实际场景

再引入一个“自己造的轮子” —— list :

namespace MyTest
{template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef ListNode<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* x):_node(x){}// ++itself& operator++(){_node = _node->_next;return *this;}// it++self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};template<class T>class list{typedef ListNode<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;list(){_head = new Node(T());_head->_prev = _head;_head->_next = _head;}iterator begin(){return _head->_next;}iterator end(){return _head;}void swap(list<T>& tmp){std::swap(_head, tmp._head);}void push_back(const T& x){insert(end(), x);}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newNode = new Node(x);// prev newNode curNode* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;return newNode;}private:Node* _head = nullptr;};
}

push_back() 加上右值版本:

template<class T>
struct ListNode
{ListNode(T&& x):_next(nullptr),_prev(nullptr),_data(forward<T>(x))// 完美转发{}
};template<class T>
class list
{void push_back(T&& x){insert(end(), forward<T>(x));// 关键处,完美转发}iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newNode = new Node(forward<T>(x));// 完美转发// prev newNode curNode* prev = cur->_prev;prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;return newNode;}
};

4. 针对 移动构造 和 移动赋值重载

  • 如果自己没有实现 移动构造移动赋值重载,且没有实现析构函数、拷贝构造、赋值重载的任意一个,编译器会自动生成默认的移动构造或移动赋值重载。

​ 内置类型,完成浅拷贝;自定义类型,调用实现的移动构造(没实现移动构造,则调用拷贝构造)。

  • 如果提供了移动构造或移动赋值,则编译器不会自动生成拷贝构造和拷贝赋值

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

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

相关文章

使用星鸾云GPU云服务器搭配Jupyter Lab,创建个人AI大模型

最近我们公司IT部门宣布了一个大事情&#xff0c;他们开发了一款内部用的大模型&#xff0c;叫作一号AI员工&#xff08;其实就是一个聊天机器人&#xff09;&#xff0c;这个一号员工可以回答所有关于公司财务、人事、制度、产品方面的问题。 我问了句&#xff1a;公司加班有…

GMT6绘制北半球

设置绘制区域及投影方式 投影方式选择立体等角投影&#xff0c;在GMT6中的命令是-Js # 定义区域变量和投影变量&#xff0c;纬度从北纬30度到极点 region-180/180/30/90 projection0/90/1:60000000 gmt set PROJ_ELLIPSOID WGS-84定义CPT及地形展示 现在定义一个CPT用于显示…

欧式家居官网源码系统-轻奢大气设计风格

一款家居家私的官方网站系统&#xff0c;设计轻奢大气。 前端内容均可通过后台修改。当然你也可以用于其他行业的官网使用&#xff0c;只要你喜欢这个设计。 大致功能&#xff1a; 1、会员系统 2、支付功能 3、标签功能 4、熊掌号提交功能 5、文章发布功能 6、SEO设置功能 7、多…

[学习笔记] VFX Silhouette

目录 Part 1 : The interface of Silhouettte &#xff08;Silhouette的界面介绍&#xff09; Part 2: The shape divisions and manual roto&#xff08;形状分区和手动roto工作&#xff09;: Part 3: tracking &#xff1a; Part 4: Mocha Tracking Part 5: Motion Blur(…

【单片机毕业设计选题24004】-基于STM32和阿里云的智能鱼缸控制系统

系统功能: 此设计采用STM32单片机将采集到的环境温度,TDS值,PH值等显示在OLED上&#xff0c;并将这些信息上报至阿里云平台。系统可通过阿里云平台或按键开关加热继电器,增氧继电器,水泵继电器和舵机. 主要功能模块原理图: 电源时钟烧录接口: 单片机和按键输入电路: 继电器控…

vscode插件开发之 - menu配置

上一遍博客介绍了如何从0到1搭建vscode插件开发的base code&#xff0c;这遍博客将重点介绍如何配置menu。通常&#xff0c;开发一款插件&#xff0c;会将插件显示在VSCode 左侧的活动栏&#xff08;Activity Bar&#xff09;&#xff0c;那么如何配置让插件显示在Activity Bar…

香橙派鲲鹏Pro(orange pi kunpeng) 开箱测试,和在娱乐功能(电视盒子),深度机器学习应用方面的测试报告

摘要 对Orange Pi kunpeng这个开发板进行综合评测&#xff0c;特别关注其作为电视盒子的性能以及在深度学习应用中的算力和稳定性。通过一个月的测试&#xff0c;我们评估了其硬件性能、软件兼容性、用户体验和实际应用潜力 引言 5月份&#xff0c;我收到了csdn 对Orange P…

LabVIEW阀性能测试平台

项目背景 公司需要开发一套综合测试平台&#xff0c;用于测试汽车气压制动系统控制装置和调节装置的性能。测试平台需满足QC/T标准&#xff0c;并实现多项测试功能&#xff0c;包括密封性测试、静特性测试、动态特性测试等。公司要求系统基于LabVIEW开发&#xff0c;以便于与现…

C++ 06 之 c++增强

c06c增强.cpp #include <iostream>using namespace std; // 1、全局变量检测增强&#xff1a;可以检测出重定义 (c语言不会报错&#xff0c;但是C会报错) //int a; //int a 10;// 2、函数检测增强: 函数返回值类型、形参类型、实参个数 int sum(int a, int b) {return …

【总线】AMBA总线架构的发展历程

目录 引言 发展历程 第一代AMBA&#xff08;AMBA 1&#xff09; 第二代AMBA&#xff08;AMBA 2&#xff09; 第三代AMBA&#xff08;AMBA 3&#xff09; 第四代AMBA&#xff08;AMBA 4&#xff09; 第五代AMBA&#xff08;AMBA 5&#xff09; AMBA协议简介 ASB&#x…

JavaScript快速入门系列-3(函数基础)

第三章:函数基础 3.1 函数定义与调用3.1.1 函数声明3.1.2 函数表达式3.2 参数与返回值3.3 匿名函数与立即执行函数表达式(IIFE)3.3.1 匿名函数3.3.2 立即执行函数表达式3.4 箭头函数3.4.1 箭头函数与this3.5 函数的高级话题3.5.1 闭包3.5.2 函数柯里化3.5.3 高阶函数小结在Jav…

【C++课程学习】:Data类的实现

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f369;1.头文件 &#x1f369;2.实现文件&#xff1a; &#x1f369;3.分析&#xff1a; &…

【硬件模块】SSD1306 OLED屏幕(含GD32ESP32驱动代码)

OLED屏幕 最近在写GD32的文章&#xff0c;之前STM32有OLED屏幕来展示数据&#xff0c;ESP32可以直接打印到电脑屏幕上&#xff0c;GD32的话手上就没有办法了。 虽然是可以直接把STM32的OLED屏幕的驱动代码改改移植到GD32上面&#xff0c;不过想了想干脆写一个文章来说说如何驱…

2024牛客网高频精选Java面试八股文整理(附答案)

Java 面试 Java 作为编程语言中的 NO.1,选择入行做 IT 做编程开发的人&#xff0c;基本都把它作为首选语言,进大厂拿高薪也是大多数小伙伴们的梦想。以前 Java 岗位人才的空缺&#xff0c;而需求量又大&#xff0c;所以这种人才供不应求的现状&#xff0c;就是 Java 工程师的薪…

公用nacos,实现只调用本机相应服务,不出现负载均衡到别人机器上

当我们有两个研发同时在调试一个微服务模块时&#xff0c;你和对方本地都会启动服务&#xff0c;这就导致在nacos会同时注册两个实例。默认情况下请求这个服务&#xff0c;具体处理请求的程序会在你和对方之间来回轮询&#xff0c;即一下你的服务一下对方的服务。 其结果就导…

栈(Stack)汇总

栈简介 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或者删除操作的线性表。它的操作特性可以概括为——后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。栈顶&#xff08;Top&#xff09;——线性表允许进行插入删除的一端&#xff1b; 栈底…

【Linux】基础IO——文件描述符,重定向

话接上篇&#xff1a; 1.文件描述符fd 磁盘文件 VS 内存文件&#xff1f; 当文件存储在磁盘当中时&#xff0c;我们将其称之为磁盘文件&#xff0c;而当磁盘文件被加载到内存当中后&#xff0c;我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程…

红队内网攻防渗透:内网渗透之Linux内网权限提升技术:udf提权Capability权限LD_PRELOAD环境变量

红队内网攻防渗透 1. 内网权限提升技术1.1 Linux系统提权-Web&用户-数据库udf提权1.1.1 信息收集1.1.2 Web权限获取1.1.3 MYSQL-UDF提权1.1.4 下载到目标上1.1.5 连接确认是否有条件进行导出调用1.1.6 开始进行写入导出调用1.2 Linux系统提权-Web&用户-Capability能力1…

ThinkBook 16 2024 Ubuntu 触控板问题解决

sudo insmod goodix-gt7868q.ko sudo cp local-overrides.quirks /etc/libinput/local-overrides.quirks sudo systemctl restart gdm 有偿解决&#xff0c;无效退款 联系前&#xff0c;请写明笔记本型号和ubuntu版本

生命在于学习——Python人工智能原理(3.3)

三、深度学习 4、激活函数 激活函数的主要作用是对神经元获得的输入进行非线性变换&#xff0c;以此反映神经元的非线性特性。常见的激活函数有线性激活函数、符号激活函数、Sigmod激活函数、双曲正切激活函数、高斯激活函数、ReLU激活函数。 &#xff08;1&#xff09;线性…