C++11:右值引用 -- 移动构造和移动赋值

news2024/11/20 13:30:40

目录

一. 左值引用和右值引用的概念和性质

1.1 什么是左值引用和右值引用

1.2 左值引用和右值引用的性质

二. 移动构造和移动赋值

2.1 左值引用的缺陷

2.2 临时对象返回减少拷贝的问题(移动构造和移动赋值)

2.3 C++11 STL容器接口的一些变化

三. 完美转发问题

3.1 模板&& -- 万能引用

3.2 完美转发forward


一. 左值引用和右值引用的概念和性质

1.1 什么是左值引用和右值引用

要明确左值引用和右值引用的概念,就要先明确什么是左值和右值。

  • 左值:可以取到地址的数据表达式。除了被const修饰的左值,都可以被赋值。
  • 右值:无法取地址的数据表达式,主要包括:字面常量、函数返回值等。

注意:左值可以出现在赋值符号的左边和右边,但是右值只能出现在赋值符号的右边。

在C++11中,对于内置类型的右值称为普通右值,对自定义类型的右值称为将亡值。顾名思义,左值引用就是左值的别名,右值引用就是右值的别名。

右值引用的语法为:类型&& 引用变量名称 = 被引右值

代码1.1:左值引用和右值引用

int main()
{
	int x = 10;
	int y = 20;

	//左值引用
	int& r1 = x;   

	//右值引用
	int&& rr1 = x + y;
	int&& rr2 = 10;

	return 0;
}

1.2 左值引用和右值引用的性质

  • 右值是无法取地址的,但是给某个右值去别名之后,会在内存中的某个位置存储其值,这样就可以获取地址,也可以修改变量值。

代码1.2:取右值引用的地址

int main()
{
	int&& rr = 10;
	std::cout << &rr << std::endl;
	rr = 20;
	std::cout << rr << std::endl;    //输出20
	return 0;
}
  • 一般情况下,左值引用不能作为右值的别名,但是经过const修饰的左值引用,可以作为右值的别名。
  • 一般情况下右值引用也不能作为左值的别名,但是经过move处理后的左值,可以用右值引用作为其别名。move函数并没有移动什么内容,move仅是将左值强制转换为右值引用,实现语义的转换。
  • move要谨慎使用,因为它会将对象资源转移的权利赋给其他人(见图1.1)。

代码1.3:左右值引用之间的赋值

int main()
{
	//int& rr1 = 10;   //报错
	const int&& rr2 = 10;

	int x = 10;
	//int&& rr3 = x;  //报错
	int&& rr4 = std::move(x);

	//x还可以被继续赋给左值引用,更改rr4也会更改x
	int& rx = x;
	rr4 = 100;
	std::cout << x << std::endl;   //输出100

	return 0;
}
图1.1 move移交资源转移权限

二. 移动构造和移动赋值

采用模拟实现的String,来演示移动赋值和移动构造的实现。

namespace zhang
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//std::cout << "string(char* str)" << std::endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		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)
		{
			std::cout << "string(const string& s) -- 拷贝构造(深拷贝)" << std::endl;
			/*string tmp(s._str);
			swap(tmp);*/

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

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

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;

			return *this;
		}

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

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

		~string()
		{
			//std::cout << "析构" << std::endl;
			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
	};

	zhang::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

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

2.1 左值引用的缺陷

左值引用的缺陷主要体现在以局部自定义类型对象作为函数的返回值时,由于出了作用域对象就会被销毁,如果直接返回其引用,就会出现引用所指向的那块空间被销毁的问题。

因此,返回局部对象就不得不采用传值返回。传值返回,则至少调用一次拷贝构造函数,对于一些版本较老的编译器,甚至会调用两次拷贝构造,因为函数返回值要先拷贝构造给一个临时对象,然后再拷贝构造给接收它的对象。

图2.1 构造对象接收值返回函数的返回对象

如果是采用一个已经存在的对象,通过赋值来接收函数的返回值,那么就必须要拷贝构造一次、赋值一次,无法进一步优化。

图2.2 已存在对象接收值返回函数的返回对象

2.2 临时对象返回减少拷贝的问题(移动构造和移动赋值)

在C++98还未出现右值引用时,有以下两种方法可以接收返回值。

  • 返回全局变量的左值引用
  • 采用输出型参数 -- to_string(int val, string& str)

但是,采用全局变量存在线程安全问题,采用输出型参数则影响函数原本的形态,C++11的右值引用可以解决返回局部对象时的拷贝问题。

为此,定义一个移动构造函数string(string&& s)和一个移动拷贝函数string& operator(string&& s),用于接收自定义类型右值(将亡值)作为参数。

移动构造和移动赋值,是利用生命周期马上要结束的自定义类型对象,将生命即将结束的对象的资源传递给其他对象或将要构造出来的对象,然后带走接收将亡值的对象原本的资源,将其析构释放,这样就完成了资源的转移,从而减少拷贝的消耗。

图2.3 移动构造的过程

代码2.1:string的移动构造和移动赋值 

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

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

在string类中定义了移动构造和移动赋值之后,编译器在拷贝值返回函数的返回值时,由于返回值出了函数作用域马上会消亡,编译器会自动识别其为右值,然后调用移动构造函数完成返回值的接收,这样就避免了拷贝的消耗。

一般来说,编译的会对返回值接收的过程进行优化,直接用返回的右值对象作为参数,移动拷贝构造新对象,这样只用调用一次移动构造函数。如果用版本较老的编译器,则先通过移动构造创建临时对象,然后在拷贝给接收它的新对象,这样会调用两次移动构造函数。

图2.4 移动构造接收函数返回值的流程

2.3 C++11 STL容器接口的一些变化

C++11所有的STL容器接口,只要涉及到构造和插入新的值,都会加入右值引用版本(见图2.5),如果传递的参数是右值,就会进行资源转移,以此来减少拷贝的消耗,这在C++98中是不存在的。

C++11 新增的一些STL容器接口函数

三. 完美转发问题

3.1 模板&& -- 万能引用

对于模板函数Func(T&& t),参数t可以被称为万能引用,其可以接收左值,也可以接收右值,T&&由此被称为万能引用。

如果Func(T&& t)函数中的子函数_func(t)要传递参数t,那么默认情况下,经万能引用接收的参数全部被处理成了左值(参考图3.1代码3.1的运行结果)。因此,模板T&&也可以称为引用折叠,即:将两个&&折叠为一个&,右值变左值,左值还是左值。

注意:万能引用仅对模板&&有效,如果是确定类型的&&,则只能接收右值。

代码3.1:万能引用

void _func(int& t) { std::cout << "左值引用" << std::endl; }
void _func(const int& t) { std::cout << "常量左值引用" << std::endl; }
void _func(int&& t) { std::cout << "右值引用" << std::endl; }
void _func(const int&& t) { std::cout << "常量右值引用" << std::endl; }

template<class T>
void Func(T&& t)
{
	//T&& -- 万能引用
	_func(t);
}

int main()
{
	int x = 10;
	Func(x);     //传左值
	Func(std::move(x));   //传右值

	const int y = 10;
	Func(y);     //传右值
	Func(std::move(y));  //传常量右值

	return 0;
}
图3.1  代码3.1的运行结果

3.2 完美转发forward

万能引用T&&将参数变为左值,如果还想要让其保持原有的类型(右值)在作为参数传递给子函数,则要经参数t经函数std::forward做完美转发处理,语法为std::forward<T>(t)。

代码3.2:完美转发

void _func(int& t) { std::cout << "左值引用" << std::endl; }
void _func(const int& t) { std::cout << "常量左值引用" << std::endl; }
void _func(int&& t) { std::cout << "右值引用" << std::endl; }
void _func(const int&& t) { std::cout << "常量右值引用" << std::endl; }

template<class T>
void Func(T&& t)
{
	//forward -- 完美转发
	_func(std::forward<T>(t));
}

int main()
{
	int x = 10;
	Func(x);     //传左值
	Func(std::move(x));   //传右值

	const int y = 10;
	Func(y);     //传右值
	Func(std::move(y));  //传常量右值

	return 0;
}
图3.2 代码3.2运行结果

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

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

相关文章

【C++进阶之路】手把手教你使用string类的接口

文章目录 前言基本认识基本使用 一.构造函数默认构造函数拷贝构造函数其它构造函数①string(const char* s)②string(size_t n, char c)③string (const string& str, size_t pos, size_t len npos) 二.容量接口①size与length②max_size③capacity④empty⑤clear⑥revers…

Elastic Learned Sparse Encoder 简介:Elastic 用于语义搜索的 AI 模型

作者&#xff1a;Aris Papadopoulos, Gilad Gal 寻找意义&#xff0c;而不仅仅是文字 我们很高兴地与大家分享&#xff0c;在 8.8 中&#xff0c;Elastic 提供开箱即用的语义搜索。语义搜索旨在根据文本的意图或含义进行搜索&#xff0c;而不是词汇匹配或关键字查询。与传统的…

华为云服务器租用费用及CPU性能(1核2G/2核4G/4核8G)

华为云HECS云服务器即云耀云服务器&#xff0c;类似于阿里云和腾讯云的轻量应用服务器&#xff0c;HECS云服务器1核2G配置39.02元一年、2核4G配置99元一年、4核8G配置69.94元3个月&#xff0c;华为云百科分享华为云HECS云服务器租用费用及CPU性能详解&#xff1a; 目录 华为云…

图解LeetCode链表题

&#x1f490;文章导读 本篇文章主要详细的用图解的方式为大家讲解了简单程度的链表题&#xff0c;如果题中有错误的地方&#xff0c;还麻烦您在评论区指出&#xff0c;你的意见就是我最大的进步&#xff01;&#xff01;&#xff01; &#x1f490;专栏导读 &#x1f934;作者…

什么是数字化?企业为什么要数字化转型

一、什么是数字化&#xff1f; 什么是数字化&#xff1f;在我理解&#xff0c;数字化是一个基于时代科技发展所产生的概念&#xff0c;首先它是一个工具&#xff0c;在企业的经营发展中将信息技术融入到传统的企业模式中&#xff0c;起到了转型的作用。 其次数字化转型是企业…

国产易灵思FPGA的FIFO应用详解

一、软件设置界面 FIFO&#xff08;First In First Out&#xff0c;即先入先出&#xff09;&#xff0c;是一种数据缓冲器&#xff0c;用来实现数据先入先出的读写。与 ROM 或 RAM 的按地址读写方式不同&#xff0c; FIFO 的读写遵循“先进先出”的原则&#xff0c;即数据按顺…

关于this->moveToThread(this)——QtWidgets

前言 官方关于QThread的用法有两种&#xff1a;一是子类QThread&#xff0c;并重新实现run&#xff1b;二是使用QObject::MoveToThread&#xff0c;通过信号槽在不同的线程内通信。 最近看到了一种写法&#xff0c;就是将两者融合就是子类QThread&#xff0c;然后this->mo…

【小程序】封装时间选择组件:用单元格van-cell和插槽slot,包括起始时间和终止时间

效果 可以选择起始时间和终止时间&#xff0c;并显示。 时间选择器放在van-cell的value插槽中。 用的库&#xff1a; https://vant-contrib.gitee.io/vant-weapp/#/home https://dayjs.fenxianglu.cn/category/ 用的组件&#xff1a;Cell单元格、DatetimePicker时间选择、Pop…

【Unity编辑器扩展】(二)PSD转UGUI Prefab, 图层解析和碎图导出

书接上回&#xff1a;【Unity编辑器扩展】(一)PSD转UGUI Prefab, Aspose.PSD和Harmony库的使用_TopGames的博客-CSDN博客 工具使用预览&#xff1a; 工具目标&#xff1a; 1. 实现将psd解析生成为UI预制体&#xff0c;并导出UI图片。需支持UGUI和TextMeshProGUI, 如Button、To…

文心一言 VS 讯飞星火 VS chatgpt (22)-- 算法导论4.2 2题

二、为Strassen算法编写伪代码。 文心一言&#xff1a; Strassen算法是一种用于矩阵乘法的算法&#xff0c;它的核心思想是通过不断将原始矩阵分割成较小的子矩阵来实现高效的乘法运算。以下是Strassen算法的伪代码&#xff1a; function Strassen(A, B): if A[0][0] 0 or…

优化|数学软件是如何求解线性方程Ax=b ?

编者按 对于大家来说&#xff0c;我们从学会多项式开始就得和求解矩阵方程打交道。大学之前靠手算&#xff0c;到了大学阶段我们学会了使用科学计算软件&#xff0c;然后只需要输入简单的一行指令 x A \ b x A \backslash b xA\b&#xff0c;就可以轻轻松松求解方程组 A x …

html实现酷炫科技风大屏风格模板(附源码)

文章目录 1.设计来源1.1 大屏主界面1.2 弹框界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/130870963 html实现酷炫科技风大屏风格模板源码 &#xff0c;html大屏源码…

docker容器postgres数据导出命令及还原数据命令

参考资料 docker控制postgers容器导入导出数据_docker 导出数据库_奔跑的痕迹的博客-CSDN博客 --进入容器 docker exec -it 容器名称或容器id /bin/bash 以下命令是在进入容器执行的 --导出单张表的备份语句&#xff08;copy模式&#xff09; pg_dump -h 127.0.0.1 -U …

授权管理再工控安全中起到什么作用?

随着计算机技术、通信技术和控制技术的发展&#xff0c;工业自动化控制已经开始向网络化方向发展&#xff0c;从最初的CCS&#xff08;计算机集中控制系统&#xff09;&#xff0c;到第二代的DCS&#xff08;分散控制系统&#xff09;&#xff0c;发展到现在流行的FCS&#xff…

力扣 1775.通过最少操作次数使数的和相等、1014.最佳观光组合、33.搜索旋转排序数组

算法总结 最近作者在坚持每日一道中等难度算法题&#xff0c;也是作者考核时经常会碰到的难度&#xff0c;由于经常是到22:30才意识到自己并没有写算法&#xff0c;因此都是打开LeetCode网站随机一题&#xff0c;并未系统性的去学习&#xff0c;这一点值得反思。在做题过程中经…

航天科技AIRIOT物联会【智慧物联主题沙龙】在沈阳举办

2023年5月24日&#xff0c;由航天科技控股集团股份有限公司&#xff08;简称“航天科技”&#xff09;智慧物联事业部主办的《AIRIOT物联会-智慧物联主题分享沙龙》在沈阳举办&#xff0c;此次会议邀请到来自光伏、燃气、能源、水务、园区、工厂等行业的众多企业代表参加&#…

0起步用GPT+知乎赚了点小钱,人人可复制

大家好&#xff0c;我是五竹。 前段时间分享了一篇关于用ChatGPT赚点小钱的实战&#xff1a;TMD&#xff0c;被人偷窥了一个月&#xff01;结果上周末的时候在知乎追了一个关于Claude的热点&#xff0c;发布了一篇注册Claude的文章&#xff0c;结果小小的“爆了”一下&#xf…

Qt文件系统源码分析—第五篇QTemporaryFile

深度 本文主要分析Windows平台&#xff0c;Mac、Linux暂不涉及 本文只分析到Win32 API/Windows Com组件/STL库函数层次&#xff0c;再下层代码不做探究 本文QT版本5.15.2 类关系图 QTemporaryFile继承QFile QFile、QSaveFile继承QFileDevice QFileDevice继承QIODevice Q…

如何查看一个 docker 镜像有哪些版本

如何查看一个 docker 镜像有哪些版本 因为通过 docker search 并不能查看某个镜像的版本信息&#xff0c;如我需要特定版本的 redis 那怎么办呢~ 本文提供了如下几种方式&#xff0c;大家可以分别逐个尝试下~ 为什么有几种方式呢&#xff0c;因为官方的查找镜像网址 Docker H…

使用audition测试USBaudio数据回传延时

一&#xff0c;简介 本文主要介绍如何使用Audition软件来测试STM32 USB audio上行音频数据的延时。 二&#xff0c;准备工作 Audition&#xff0c;ASIO驱动&#xff0c;STM32枚举的USB Audio高速声卡测试板。 二&#xff0c;硬件连接 将STM32的IIS的data in和data out使用…