右值引用带来的效率提升(C++11)

news2024/11/18 13:55:38

在这里插入图片描述

文章目录

  • 一.左值引用和右值引用
  • 二.C++11区分左值和右值的语法设计意义--对象的移动构造和移动赋值
    • 场景分析1:
      • C++11之前
      • C++11之后
    • 场景分析2:
    • 函数std::move
    • 右值引用的广泛使用
  • 三.引用折叠

一.左值引用和右值引用

  • 左值:可以取到地址的对象(可以出现在赋值符号的左边),对左值的引用称为左值引用&.
  • 右值(将亡值):无法取到地址的对象(不能出现在赋值符号的左边),对右值的引用称为右值引用&&.
    • 常见的右值有:字面常量,表达式返回值,函数返回值(非const限定的引用)(通常是常量或临时变量)
void Testreference1()
{
	//以下的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;
}

void Testreference2()
{
	double x = 1.1, y = 2.2;
	//以下几个都是对右值的右值引用
	//常量
	int&& rr1 = 10;
	//表达式返回值
	double&& rr2 = (x + y);
	//非const修饰的函数返回值(fmin返回值为double)
	double&& rr3 = fmin(x, y);
}
  • const type&(被const限定的左值引用)既可以引用左值,也可以引用右值

二.C++11区分左值和右值的语法设计意义–对象的移动构造和移动赋值

  • C++11区分出左值和右值是为了能够让编译器识别出一些即将析构的类对象(将亡),当这些类对象作为引用形参拷贝给其他对象时,通过右值的类型识别,就可以调用相应重载版本拷贝构造或赋值运算符重载来实现对象堆区资源的交换转移(通过交换指向堆区的指针实现),从而避免了没必要的深拷贝

场景分析1:

C++11之前

  • C++11之前的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{
	class string
	{
	public:
		//构造函数(深拷贝)
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//交换两个string对象(堆区资源交换)
		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;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; 
	};
}
  • 执行以下代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	string s(to_string());
	return 0;
}

在这里插入图片描述

  • 类似的场景下,在C++11之前,要接收tem的数据就会进行对象的深拷贝:在这里插入图片描述
  • tem完成拷贝后就会调用析构函数释放其内存资源,这种情况下tem的堆区资源其实是被浪费掉的,如果不进行深拷贝,直接将tem对象与s对象中的指针进行交换,就可以实现堆区资源的转移,但是这种指针交换操作会让被拷贝的对象无法再使用,因此就需要确定被拷贝的对象是否为即将析构的对象,于是C++区分出了左值和右值来识别一些即将析构的对象

C++11之后

  • C++11之后的string对象模拟(只含对象的构造,拷贝构造,赋值重载,析构函数):
namespace mystring
{
	class string
	{
	public:
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//交换两个string对象(堆区资源交换)
		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(string && s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			_str = new char[_capacity + 1];
			cout << "string(string&& s) -- 移动构造(资源转移)" << endl;
			swap(s);
		}
		//移动赋值
		string& operator=(string && s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值(资源转移)" << endl;
			swap(s);
			return *this;
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; 
	};
}
  • C++11之后类对象新增了两个默认成员函数:移动赋值移动构造函数(本质上就是构造函数赋值运算符重载函数形参为右值引用类型的重载版本)
  • 当类对象直接申请了堆区内存,移动赋值和移动构造函数就需要开发者自行将其实现用于对象拷贝的场景,实现方式一般就是交换两个对象指向堆区内存的指针.
  • 执行同样的代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	//函数返回值被识别成右值,调用移动构造交换堆区资源,避免了深拷贝
	string s(to_string());
	return 0;
}

在这里插入图片描述

  • tem被识别成了右值(将亡值),s的创建调用了形参为右值引用的构造函数重载版本(也就是移动构造),进行了s对象和tem对象的堆区资源交换,避免了深拷贝在这里插入图片描述

场景分析2:

  • 使用前述的C++11之前的string对象C++11之后的string对象分别执行以下代码:
string to_string()
{
	string tem("对象测试");
	return tem;
}
//tem在函数调用完后就是一个即将析构的对象
int main()
{
	string s;
	s = to_string();
	return 0;
}

在这里插入图片描述

  • 上面类似的场景中,移动构造移动赋值避免了两次深拷贝

函数std::move

  • 当一个左值对象需要被拷贝并且拷贝完后不再使用,它作为对象拷贝函数的引用形参时,可以使用move函数将其强制识别为右值,比如:
int main()
{
	//测试构造函数

	string s1("对象测试");
	//s1是左值对象,调用拷贝构造
	string s2(s1);
	//move强制让编译器将s1识别成右值,调用移动构造交换堆区资源
	string s3(std::move(s1));
	
	cout << endl;
	//测试赋值重载函数

	//函数返回值被识别成右值,调用移动赋值交换堆区资源,避免了深拷贝
	string s4("对象测试");
	//s4是左值对象,调用赋值重载
	s2 = s4;
	//move强制让编译器将s1识别成右值,调用移动赋值,交换堆区资源
	s3 = std::move(s1);
}

在这里插入图片描述

右值引用的广泛使用

  • C++11之后,STL标准模板库中的所有数据结构对象中,所有可能涉及对象深拷贝的成员接口(包括对象的构造函数,赋值重载函数以及增删查改接口)都增加了形参为对象右值引用的重载版本,从而有效地减少了STL在使用的过程中对象深拷贝的次数.类对象移动构造和移动赋值的设计模式,一定程度上提高了C++代码的时间效率

三.引用折叠

  • 引用折叠的概念:作为类型模板参数的引用可以起到引用折叠的作用,既可以接收左值引用也可以接收右值引用
template<typename T>
void PerfectForward(T&& t)
{
}
  • 代码段中的函数形参t既可以接收左值引用也可以接收右值引用,但是当t接收到右值引用时,会将其转化为左值引用(底层机制是在内存中开一段空间存储对象),我们希望能够在引用传递的过程中保持它的左值或者右值的属性,这时就需要std::forward函数来辅助引用参数传递(称为完美转发):在这里插入图片描述
int main()
{
	string s1("对象测试");

	list<string> List;
	List.push_back(std::move(s1));
}
  • 上面的STL的使用场景中就发生了引用的完美转发,list的push_back接口的内部代码结构:
template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	//复用Insert实现
	void push_back(T&& x)//引用折叠
	{
		//引用完美转发
		Insert(_head, std::forward<T>(x));
	}
	//链表结点插入接口
	void Insert(Node* pos, T&& x)//引用折叠
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		//引用完美转发
		newnode->_data = std::forward<T>(x);
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};

在这里插入图片描述

  • 最终s1的右值引用传递到了string对象的移动赋值函数中,避免了s1对象的深拷贝
    在这里插入图片描述

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

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

相关文章

“Rust难学”只是一个谎言

近年来Rust的存在感日渐升高&#xff0c;但是其陡峭的学习曲线似乎总是令人望而生畏。不过谷歌的一项内部调查表明&#xff0c;关于Rust的“难学”或许只是一种谣传。 Rust到底难不难学&#xff1f;谷歌有了Go&#xff0c;为何还要支持Rust&#xff1f;频频陷入内斗的Rust领导…

FPGA学习——蜂鸣器实现音乐播放器并播放两只老虎

文章目录 一、蜂鸣器简介1.1 蜂鸣器分类1.2 PWM 二、C4开发板原理图三、如何产生不同的音调四、代码实现及分析五、总结 一、蜂鸣器简介 1.1 蜂鸣器分类 蜂鸣器一般分为有源蜂鸣器和无源蜂鸣器。二者的区别在于&#xff0c;有源蜂鸣器内部含有振动源和功放电路&#xff0c;只…

【雕爷学编程】 MicroPython动手做(35)——体验小游戏3

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

计算机和汇编语言

1.用电表示数字 我们已经学习过二进制来表示数字 二进制计数采用0和1组合表示数字 0和1很适合使用开关闭合&#xff0c;导线上有电流是1&#xff0c;无电流是 我们还可以加上小灯泡&#xff0c;来表示数 2.二进制加法机 上述这个加法机器是接受左边和下面的输入&#xff0c;把…

【沁恒蓝牙mesh】CH58x系统时钟配置与计算

本文主要记录了【沁恒蓝牙mesh】CH58x系统时钟配置与计算 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#x1f4dd; 个人主页&#xff1a;欢迎访问我的 Ethernet_Comm 博客主页&#x1f525;&#x1f389; 支持我&am…

侧边栏的打开与收起

侧边栏的打开与收起 <template><div class"box"><div class"sideBar" :class"showBox ? : controller-box-hide"><div class"showBnt" click"showBox!showBox"><i class"el-icon-arrow-r…

云渲染:为你的设计作品增添细节与逼真感!

在设计作品中&#xff0c;细节和逼真感是展现作品品质和吸引观众眼球的关键要素。而云渲染技术则是让我们能够以更高的水平来增添细节和逼真感的利器。让我们一起深入了解云渲染&#xff0c;探索它如何为我们的设计作品带来更出色的效果。 云渲染技术利用云计算的强大能力&…

webshell详解

Webshell详解 一、 Webshell 介绍二 、 基础常见webshell案例 一、 Webshell 介绍 概念 webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境&#xff0c;也可以将其称做为一种网页后门。黑客在入侵了一个网站后&#xff0c;通常会将asp或php后门文件与…

千元左右初学者性价比吉他推荐,VEAZEN费森VZ90和布鲁克S25怎么样?各方面评测对比,哪一款更出众!

在1500元左右价位里的吉他品牌来说&#xff0c;可谓群雄割据&#xff0c;根本无法判断到底是谁更出众。那么今天就用这个价位里我们觉得比较受欢迎的两款产品&#xff0c;VEAZEN费森VZ90系列和BROOK布鲁克S25系列详细对比评测&#xff0c;希望能给琴友一个很好的选择参考。 15…

批量创建可配置物料参数文件

启用可配置物料之后&#xff0c;每次创建新的物料需要通过CU41创建可配置物料&#xff0c;没找大批量创建的程序&#xff0c;所以SHDB录屏搞了一个代码。 前提&#xff1a;物料主数据初始化通过程序导入时&#xff0c;可配置物料参数文件已按照物料代码赋值。 ​效果&#xf…

同比环比使用方法

一、解释&#xff1a; 1.同比&#xff1a;本期与去年同期相比 &#xff0c;如2023年8月 比 2022年8月 2.环比&#xff1a;本期与上期相比 &#xff0c;如2023年8月 比 2023年7月 二、应用&#xff1a; 1.场景&#xff1a;统计日报、周报、月报、年报下进店客流的同比和环…

Stable Diffusion教程(6) - 图片高清放大

放大后细节 修复图片损坏 显存占用 速度 批量放大 文生图放大 好 是 高 慢 否 附加功能放大 一般 否 中 快 是 图生图放大 好 是 低 慢 是 tile模型放大 非常好 是 高 快 是 通过文生图页面的高清修复 优点&#xff1a;放大时能添加更多细节&am…

LeetCode--HOT100题(18)

目录 题目描述&#xff1a;73. 矩阵置零&#xff08;中等&#xff09;题目接口解题思路1代码解题思路2代码 PS: 题目描述&#xff1a;73. 矩阵置零&#xff08;中等&#xff09; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都…

【Linux】网络基础之IP协议

目录 &#x1f338;1、基本概念&#x1f33a;2、IP协议报文结构&#x1f368;2.1、4位版本号&#x1f369;2.2、4位首部长度和16位总长度&#x1f36a;2.3、8位生存时间&#xff08;TTL&#xff09;&#x1f36b;2.4、8位协议&#x1f36c;2.5、16位首部校验和&#x1f36d;2.6…

Manim(一款强大的数学可视化动画引擎)学习历程

相逢情便深&#xff0c;恨不相逢早 第一眼看见上面这种类型的视频我就深深被它的简约清楚所折服&#xff0c;我觉得它完全符合我的审美&#xff0c;我也相信只要了解过制作这种视频的软件的人都会喜欢上它。运用这种风格比较有名的是b站里的一位up主名叫3Blue1Brown&#xff0…

vue卡片轮播图

我的项目是vue3的&#xff0c;用的swiper8 <template><div class"tab-all"><div class"tab-four"><swiper:loop"true":autoplay"{disableOnInteraction:false,delay:3000}":slides-per-view"3":center…

快速上手字符串函数

文章目录 前言一、求字符串的长度strlen函数strlen函数学习使用strlen函数模拟实现strlen函数模拟实现方法1&#xff1a;计数器法strlen函数模拟实现方法2&#xff1a;指针减指针法strlen函数模拟实现方法3&#xff1a;递归方法 二、字符串的拷贝&#xff0c;拼接和比较strcpy函…

某科技公司提前批测试岗

文章目录 题目 今天给大家带来一家提前批测试岗的真题&#xff0c;目前已经发offer 题目 1.自我介绍 2.登录页面测试用例设计 3.如何模拟多用户登录 可以使用Jmeter,loadRunner性能测试工具来模拟大量用户登录操作去观察一些参数变化 4.有使用过Jmeter,loadRunner做过性能压…

为什么 CSS 这么难学?

前言 CSS难其实就难在其内容的多变上&#xff0c;我觉得这些其实都可以通过大量的练习来解决&#xff0c;去记再多的东西不如写几个demo或者小项目来的收获大&#xff0c;当然练完项目所需要的总结是必需的&#xff01;下面我推荐整理了一些学习css相关的网站和项目&#xff0…

谁会拒绝一篇关于【python装饰器】的友情分享呢~

一、什么是装饰器 1. python装饰器是用于拓展原来函数功能的一种函数&#xff0c;目的是在不改变原函数的情况下&#xff0c;给函数增加功能。2. 装饰器是通过闭包实现&#xff0c;所以讲装饰器首先得知道什么是闭包。 二、什么是闭包 1、什么是闭包 1. 一个定义在函数内部的…