【C++练级之路】【Lv.22】C++11——右值引用和移动语义



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、右值引用
    • 1.1 左值和右值
    • 1.2 左值引用和右值引用的范围
    • 1.3 左值引用的意义
  • 二、移动语义
    • 2.1 移动构造
    • 2.2 移动赋值
    • 2.3 右值引用的意义
    • 2.4 move
    • 2.5 移动插入
  • 三、完美转发
    • 3.1 万能引用
    • 3.2 forward
  • 四、新增默认成员函数
    • 4.1 移动构造函数
    • 4.2 移动赋值重载
    • 4.3 default
    • 4.4 delete

引言

关于C++11的final和override的知识,在之前已经提到过,这里不再赘述,有需要的请移步这篇博客【C++练级之路】【Lv.13】多态(你真的了解虚函数和虚函数表吗?)

一、右值引用

1.1 左值和右值

  • 左值:可取地址,可在等号左右
  • 右值:不可取地址,只能在等号右边
void test()
{
	int a;//左值
	10;//右值
	10 + 20//右值
}

一般情况下,左值均为变量名,而右值则为字面常量、表达式等。

1.2 左值引用和右值引用的范围

void test()
{
	int& ref1 = a;//左值引用,可以引用左值

	//int& ref2 = a + b;//左值引用,不能引用右值(权限放大)
	const int& ref2 = a + b;//const左值引用,可以引用右值

	int&& ref3 = a + b;//右值引用,可以引用右值

	//int&& ref4 = a;//右值引用,不能引用左值
	int&& ref4 = move(a);//右值引用,可以引用move后的左值
}
  • 左值引用,可以引用左值
  • const左值引用,可以引用右值
  • 右值引用,可以引用右值
  • 右值引用,可以引用move后的左值

ps:move的作用,是将左值强制转换为右值引用,详情见move章节。
ps:右值的引用属性为左值,将右值引用后,右值会被存储起来,并可以取到地址。

1.3 左值引用的意义

左值引用:

  1. 传引用传参,减少拷贝
  2. 传引用返回,减少拷贝(限制:函数内的局部对象,不能传引用返回)

左值引用已经解决了绝大多数拷贝问题,但是唯一的缺陷就是不能传引用返回局部对象。所以,这就是右值引用存在的意义,为了补全这块不足。

而要完全理解右值引用的意义,则需要学习移动语义,理解右值引用是如何减少拷贝的。

二、移动语义

首先,给出一个自己实现的精简版string类,方便调试和观察内部细节。

namespace my
{
	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);
		}

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(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;
			}
		}

		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)
		{
			push_back(ch);
			return *this;
		}

		string operator+(char ch)
		{
			string tmp = *this;
			tmp += ch;
			return tmp;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

operator+是我们要重点观察的函数,特意拿出来方便对比:

string operator+(char ch)
{
	string tmp = *this;
	tmp += ch;
	return tmp;
}

2.1 移动构造

先来看看以下代码:

void test()
{
	my::string s1 = "hello";
	my::string s2 = s1 + '!';
}

在以往的经验中,operator+中的tmp(原始对象)传值返回,先拷贝构造给临时对象,tmp在函数域内销毁,然后临时对象再拷贝构造给s2(目标对象),总共有两次深拷贝。

但是,如果运用上右值引用的移动构造,加上以下代码:

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

此时,operator+中的tmp(原始对象)传值返回,直接和s2互换资源(称之为移动),就可以直接无拷贝返回,直接减少了两次深拷贝。


ps:如果符合编译器优化,编译器会自动将tmp识别为左值,从而将连续三次拷贝构造优化成一次。(VS2022)
ps:如果不符合优化,编译器才会将tmp强制识别为右值,从而符合移动语义。
ps:如果只有const&,右值会匹配;如果有&&,右值则会匹配更适合的。


2.2 移动赋值

同理,再看看这段代码:

void test()
{
	my::string s1 = "hello";
	my::string s2;
	s2 = s1 + '!';
}

在以往的经验中,operator+中的tmp(原始对象)传值返回,先拷贝构造给临时对象,tmp在函数域内销毁,然后临时对象再赋值给s2(目标对象),总共有两次深拷贝。

但是,如果运用上右值引用的移动赋值,加上以下代码:

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

此时,operator+中的tmp(原始对象)传值返回,直接和s2互换资源(称之为移动),就可以直接无拷贝返回,直接减少了两次深拷贝。

ps:此时不是连续的拷贝构造,而是拷贝构造+赋值,所以编译器不会优化。

2.3 右值引用的意义

对于函数内的右值,分为两类:

  • 纯右值:内置类型的右值
  • 将亡值:自定义类型的右值

右值引用:把将亡值的资源直接移动,从而减少两次深拷贝

拷贝
拷贝
移动
原始对象
临时对象
目标对象

2.4 move

move 是一个模板函数,它接受一个左值引用,并返回一个右值引用。这允许我们指示编译器,我们可以安全地“移动”这个左值的资源,而不是复制它们。

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
	// forward _Arg as movable
	return ((typename remove_reference<_Ty>::type&&)_Arg);
}

ps:move 只是一个“建议”或“请求”,而不是强制。它告诉编译器:“这个对象我不再需要了,你可以安全地将其资源移动给另一个对象。”

但是,如果对象的类型没有定义移动构造函数或移动赋值运算符,或者这些函数被标记为 delete,那么编译器仍然会进行复制操作。


同时,使用move一定要慎重,如果要进行资源移动,要确保move的左值不会再使用。

void test()
{
	string s1 = "hello";
	string s2 = move(s1);
}

以上代码中,move后的s1被识别为右值,调用右值引用的移动构造,将s1的资源移动到s2,而s1本身就被置空了。

2.5 移动插入

C++11更新后,STL中所有容器都新增了移动版本的插入函数。那么,它与原先的插入函数有什么不同呢?


先来看看以下代码:

void test()
{
	vector<string> v;
	v.push_back("1111");
}

C++98:void push_back (const T& val);
先利用右值构造string,再拷贝构造插入vector。

C++11:void push_back (T&& val);
先利用右值构造string,再移动插入vector。

综上比较,移动插入相较于传统插入,减少了一次深拷贝,效率得到了提高。

ps:const& 延长右值生命周期(C++98)

三、完美转发

3.1 万能引用

template<typename T>
void PerfectForward(T&& t)//万能引用(引用折叠)
{}

函数模板参数中的T&&,不再代表右值引用,而是代表万能引用(又称引用折叠)。它能以统一的方式处理左值和右值,既能接收左值引用,也能接收右值引用

  • t为右值时,保持为T&&
  • t为左值时,折叠为T&

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

template<typename T>
void PerfectForward(T&& t)//万能引用(引用折叠)
{
	Fun(t);
}

void test()
{
	PerfectForward(10);//右值
	
	int a;
	PerfectForward(a);//左值
	PerfectForward(move(a));//右值

	const int b = 8;
	PerfectForward(b);//const 左值
	PerfectForward(move(b));//const 右值
}

前面已经提到,右值的引用属性为左值(只有这样设计才能实现移动语义),那么在上述代码中,调用Fun函数就全部是左值引用,无法达到区分左值和右值的效果。

那么,如何在传递中保持参数的属性呢?这时就要用到完美转发!


forward 是一个模板函数,如果接收左值引用,则返回左值引用,如果接收右值引用,则返回右值引用

template<typename T>
void PerfectForward(T&& t)//万能引用(引用折叠)
{
	Fun(forward<T>(t));//完美转发
}

完美转发允许函数模板将其参数以原始值类别(左值或右值)转发给另一个函数。这通常用于包装或委托函数。

四、新增默认成员函数

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}

	//Person(const Person& p)
	//	:_name(p._name)
	//	,_age(p._age)
	//{}

	//Person& operator=(const Person& p)
	//{
	//	if(this != &p)
	//	{
	//		_name = p._name;
	//		_age = p._age;
	//	}
	//	return *this;
	//}

	//~Person()
	//{}
private:
	my::string _name;
	int _age;
};

4.1 移动构造函数

若未显式定义,且未显式定义拷贝构造、拷贝赋值、析构,编译器才会自动生成默认的移动构造函数。对内置类型值拷贝,对于自定义类型调用其移动构造函数(若未显式定义,则调用其拷贝构造)

4.2 移动赋值重载

若未显式定义,且未显式定义拷贝构造、拷贝赋值、析构,编译器才会自动生成默认的移动赋值重载。对内置类型值拷贝,对于自定义类型调用其移动赋值重载(若未显式定义,则调用其拷贝赋值重载)

void test()
{
	Person s1;
	Person s2 = s1;
	Person s3 = move(s1);//移动构造
	Person s4;
	s4 = move(s2);//移动赋值
}

4.3 default

强制生成默认成员函数

Person(Person&& p) = default;//强制生成默认移动构造
Person& operator=(Person&& p) = default;//强制生成默认移动赋值

4.4 delete

禁止生成默认成员函数

Person(const Person& p) = delete;//禁止生成默认拷贝构造
Person& operator=(const Person& p) = delete;//禁止生成默认拷贝赋值

ps:C++98中,将函数设置为private,以此达到禁止生成默认成员函数的目的。


真诚点赞,手有余香

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【基础算法总结】前缀和二

前缀和二 1.和为 K 的子数组2.和可被 K 整除的子数组3.连续数组4. 矩阵区域和 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.和为 K 的子数…

哇!数据中台竟是企业数字化转型的关键力量!

在当今数字化浪潮席卷的时代&#xff0c;数据中台正成为企业实现数字化转型的关键力量。那么&#xff0c;究竟什么是数据中台呢&#xff1f;它乃是一种持续让企业数据活跃起来的机制&#xff0c;能够将企业内各部分数据汇聚至一个平台&#xff0c;达成数据的统一化管理。 数据中…

六、Prometheus服务发现

目录 一、prometheus的服务发现 1、基于文件的服务发现 二、基于consul的服务发现 一、prometheus的服务发现 Prometheus默认是采用pull的方式拉取监控数据的&#xff0c;每一个被抓取的目标都要暴露一个HTTP接口&#xff0c;prometheus通过这个接口来获取相应的指标数据&…

LED屏控制卡是如何控制LED屏的?

LED屏控制卡是LED显示屏的关键组件之一&#xff0c;负责将输入的画面信息转换为LED屏能够显示的数据和控制信号。以下是LED屏控制卡的工作原理和功能的详细介绍&#xff1a; 1. LED显示屏控制器概述&#xff1a; LED显示屏控制器是LED显示屏的核心部件之一&#xff0c;也称为LE…

Alamofire常见GET/POST等请求方式的使用,响应直接为json

Alamofire 官方仓库地址&#xff1a;https://github.com/Alamofire/Alamofire xcode中安装和使用&#xff1a;swift网络库Alamofire的安装及简单使用&#xff0c;苹果开发必备-CSDN博客 Alamofire是一个基于Swift语言开发的优秀网络请求库。它封装了底层的网络请求工作&…

亚信安全:2024攻防演练利器之必修高危漏洞合集-百度网盘下载

亚信安全:2024攻防演练利器之必修高危漏洞合集-百度网盘下载. 90% ! 2023攻防演练期间 暴露的web漏洞占比90% 覆盖VPN、远程工具、办公软件 OA系统、聊天工具、安全产品等全路径 100% ! 隐藏在暗处的高危漏洞 一旦被利用&#xff0c;被攻陷率近100% 很多企业为此导致整…

解析新加坡裸机云多IP服务器网线路综合测评解析

在数字化高速发展的今天&#xff0c;新加坡裸机云多IP服务器以其卓越的性能和稳定性&#xff0c;成为了众多企业和个人用户的首选。源库主机评测将对新加坡裸机云多IP服务器的网线路进行综合测评&#xff0c;以帮助读者更深入地了解这一产品的优势。 一、性能表现 新加坡裸机云…

Facebook开户 | Facebook的CTR是什么?

在当今数字化的营销领域&#xff0c;了解和利用各种指标是成功的关键。其中一个关键指标是CTR&#xff0c;即点击率&#xff08;Click-Through Rate&#xff09;。 在Facebook广告中&#xff0c;CTR是一个至关重要的度量标准&#xff0c;它不仅可以衡量广告的效果&#xff0c;还…

OneForall工具的下载安装和使用(Windows和Linux)

目录 OneForall的介绍 OneForall的下载 OneForall的安装 安装要求 安装步骤&#xff08;git 版&#xff09; 安装&#xff08;kali&#xff09; OneForall的使用命令 在Windows 在Linux&#xff08;kali&#xff09; OneForall的结果说明 免责声明 本文所提供的文字和…

安全风险 - 切换后台时背景模糊处理

因为安全风险中提到当app处于后台卡片状态时&#xff0c;显示的卡片页面应该为模糊效果&#xff0c;否则容易泄露用户隐私&#xff0c;尤其当前页涉及个人信息、资产信息等&#xff0c;都会造成信息泄露&#xff01;基于这种场景&#xff0c;我研究了下这种业务下的模糊效果 找…

图像处理中的维度元素复制技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、维度元素复制的基本概念 三、如何实现维度元素复制 1. 方法介绍 2. 代码示…

方正国际金融事业部副总经理白冰受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 方正国际软件&#xff08;北京&#xff09;有限公司金融事业部副总经理白冰先生受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“浅析多项目管理的成功因素”。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xf…

flinkcdc 3.0 源码学习之客户端flink-cdc-cli模块

注意 : 本文章是基于flinkcdc 3.0 版本写的 我们在前面的文章已经提到过,flinkcdc3.0版本分为4层,API接口层,Connect链接层,Composer同步任务构建层,Runtime运行时层,这篇文章会对API接口层进行一个探索.探索一下flink-cdc-cli模块,看看是如何将一个yaml配置文件转换成一个任务…

RK平台ADB不识别问题排查

简介 ADB是Android系统的调试工具&#xff0c;一般用USB线连接开发板和PC&#xff0c;可以抓取开发板的调试日志&#xff0c;执行shell指令&#xff0c;传输文件等功能。为了调试方便&#xff0c;RK平台的Linux系统也默认支持ADB&#xff0c;其源码是从Android移植过来的。 本…

Android 中资源文件夹RES/RAW和ASSETS的使用区别

文章目录 1、res/raw 文件夹1.1、特点1.2、使用方法1.3、示例&#xff1a; 2. assets 文件夹2.1、特点2.2、使用方法2.3、示例&#xff1a; 3、使用场景3.1、res/raw 使用场景3.2、assets 使用场景 4、比较与选择5、文件夹选择的建议6、 示例代码总结6.1、res/raw 示例6.2、ass…

Diffusion Model 和 Stable Diffusion 详解

文章目录 Diffusion Model 基础生成模型DDPM概述向前扩散过程前向扩散的逐步过程前向扩散的整体过程 反向去噪过程网络结构训练和推理过程训练过程推理过程优化目标 详细数学推导数学基础向前扩散过程反向去噪过程 Stable Diffusion组成结构运行流程网络结构变分自编码器 (VAE)…

图形学初识--纹理采样和Wrap方式

文章目录 前言正文1、为什么需要纹理采样&#xff1f;2、什么是纹理采样&#xff1f;3、如何进行纹理采样&#xff1f;&#xff08;1&#xff09;假设绘制区域为矩形&#xff08;2&#xff09;假设绘制区域为三角形 4、什么是纹理的Wrap方式&#xff1f;5、有哪些纹理的Wrap方式…

Facebook之魅:数字社交的体验

在当今数字化时代&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;承载着数十亿用户的社交需求和期待。它不仅仅是一个简单的网站或应用程序&#xff0c;更是一个将世界各地的人们连接在一起的社交网络&#xff0c;为用户提供了丰富多彩、无与伦比的数字社交体验。…

云原生|为什么服务网格能够轻松重塑微服务?一文讲清楚!

目录 一、概述 二、 设计 三、服务网格 四、总结 一、概述 容器化技术与容器编排推动了微服务架构应用的演进&#xff0c;于是应用的扩展与微服务的数量日益增加&#xff0c;新的问题随之而来&#xff0c;监控服务的性能变得越来越困难&#xff0c;微服务与微服务之间相互通…

深度学习实战-yolox训练ExDark数据集所遇到的错误合集

跳转深度学习实战-yolox训练ExDark数据集(附全过程代码,超详细教程,无坑!) 一、 训练时出现ap为零 情况1.数据集没导进去 修改exps/example/yolox_voc/yolox_voc_s.py 当然由于image_sets只有一个元素因此修改yolox/data/datasets/voc.py 情况2.iou设置过高 修改yolo…