首页 > 编程语言 >【C++】右值引用全面揭秘:解锁 C++11 的性能革命与移动语义奥秘!

【C++】右值引用全面揭秘:解锁 C++11 的性能革命与移动语义奥秘!

时间:2024-12-02 18:31:15浏览次数:14  
标签:11 str 右值 int 左值 C++ 引用 &&

文章目录


C++11 引入了右值引用,这是C++语言的一个重要特性,目的是为了提高程序的性能,尤其在对象的传递和资源管理方面。
右值引用和左值引用相比,解决了左值引用在传返回值的不足,显著减少了不必要的拷贝,提高效率。

右值和左值的基本概念

在 C++ 中,表达式的值可以分为左值右值两种类型:

  • 左值:表示一个持久存在的对象或者内存位置,通常在赋值语句的左侧出现,以及有可以取地址的特性。例如:变量、数组元素、解引用等都是左值。
//以下均是左值
//变量
int a = 3;
int* pa = &a;
const int b = a;
int* ptr = new int(3);

//解引用
*ptr = 4;

//数组元素
string str("abcdef");
str[0];
  • 右值:表示临时对象、字面量常量或者表达式的结果,通常只能出现在赋值语句的右侧,有不可取地址的特性。右值是没有名称的、即将被销毁的对象。
int a = 4, b = 5;

//以下均是右值
100;
a + b;
fmin(x, y);
string("qwer");

左值引用和右值引用

引用就是给对象取别名,右值引用就是给右值取别名左值引用就是给右值取别名右值引用左值引用在语法形式上是类似的:

Type& ref = x;  //左值引用

Type&& rref = y;  //右值引用

可以看到,左值引用是用 & ,而右值引用是用 &&

//左值
int a = 3;
int* pa = &a;
const int b = a;
int* ptr = new int(3);

//左值引用
int& ra = a;
int*& rpa = pa;
const int& rb = b;
int* rptr = ptr;
int a = 4, b = 5;
//右值
//100;
//a + b;
//fmin(x, y);
//string("qwer");

//右值引用
int&& rr1 = 100;
int&& rr2 = a + b;
int&& rr3 = fmin(a, b);
string&& rr4 = string("qwer");

对右值引用的理解:右值本质上是一种生命周期很短的对象(将亡值),而右值引用实际上是将该对象的地址保存,该对象就不会立即销毁,延长了生命周期。

注意,通过右值引用创建出来的对象的属性是左值,这一点非常的重要,涉及到下面提及的完美转发。


一般而言,右值引用只能引用右值,左值引用只能引用左值,但在特殊情况下,右值引用可以引用左值,左值应用也可以引用右值

  • 左值引用去引用右值:需要在前面加 const 修饰。
  • 右值引用去引用左值:需要对左值进行 move
//左值引用去引用右值,需要加const
const int& r1 = 10;
const string& r2 = string("abcd");

//右值引用求引用左值,需要对左值move
int x = 3;
int&& rr1 = move(x);
string str("1234");
string&& rr2 = move(str);

左值引用在特定条件下可以引用右值,这一点在前面其实也有所涉及,之前模拟实现容器(如 vector、list等)的 push_back 函数: void push_back (const T& x),加 const 是为了让 x 既能接收左值也能接收右值。

move 本质上就是强制类型转换,不会改变左值对象本身的属性。

template <class T>
typename remove_reference<T>::type&& move (T&& arg) noexcept;
{
	return static_cast<remove_reference<decltype(arg)>::type&&>(arg)
}

右值引用的主要用途

在右值引用出现之前,左值引用还是无法解决在某些场景下需要传值返回的问题,而右值引用的出现,实现了移动语义完美转发,显著提高C++程序在对象的的拷贝和传递的性能。

移动语义

移动语义可以分为移动构造移动赋值,其实就可以“移动”资源而不是复制资源。从而避免不必要的资源拷贝。右值引用允许了资源从一个对象转移到另一个对象,而不是创建一个新的副本。

接下来,我们就以一个自定义 string 类,来看看移动语义的作用是多么强大,还有在没有移动语义之前VS的设计者如何跟冗余构造斗智斗勇。

class string
{
public:

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

	//析构
	~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

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

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

	//赋值重载
	string& operator=(const string& s)
	{
		cout << "string& operator=(string s) -- 赋值重载" << endl;
		if (this != &s)
		{
			_str[0] = '\0';
			if (s._capacity > _capacity)
			{
				char* tmp = new char[s._capacity + 1];
				if (_str)
					delete[] _str;
					
				_str = tmp;
				_capacity = s._capacity;
			}

			strcpy(_str, s._str);
			_str[s._capacity] = '\0';
			_size = s._size;
		}

	return *this;
}

private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

只有拷贝构造和赋值重载而没有移动语义的传值返回

正常对一个自定义类传值返回是需要进行3次构造的,函数体内将构造需要返回 str 对象,在返回 str 时先对其拷贝构造出一个临时对象 tmp ,函数体外的用于接收返回值的 ret 再去拷贝构造这个 tmp 对象,很明显,这样多次构造消耗很大,效率很低。如下图:
在这里插入图片描述
聪明的编译器设计师一想,这样不慢了啊,干脆不构建临时对象,直接将 str 拷贝构造给 ret 不就行了。如下图:
在这里插入图片描述
另一位设计师看了,不行啊,你这样还是慢,看我的,直接将3次构造合三为一。如下图:
在这里插入图片描述
我们可以运行一下来验证结果,如下图:
在这里插入图片描述
结果就是编译器真的做出了 “合3为1”的极致优化来提高效率,这里真的不得不感叹下设计编译器的设计师能力是真的强

标签:11,str,右值,int,左值,C++,引用,&&
From: https://blog.csdn.net/2301_80373479/article/details/143834706

相关文章

  • C++:unordered_map与unordered_set详解
    文章目录前言一、KeyOfT1.为什么需要仿函数?2.MapKeyOfT与SetKeyOfT代码实现二、迭代器1.设计背景2.为什么需要存储哈希表指针3.operator++的逻辑4.begin()和end()的实现5.友元和前置声明的作用6.完整代码三、迭代器map与set的复用1.map的复用,数据pair<K,......
  • UniApp 微信小程序请求接口报错:request:fail errcode:10011 的原因分析与解决方案
    UniApp微信小程序请求接口报错:request:failerrcode:10011的原因分析与解决方案在使用UniApp开发微信小程序时,我们可能会遇到一些请求接口的错误,特别是request:failerrcode:10011cronet_error_code:0error_msg:networkchange,cancelalltask这样的错误信息。这......
  • C++中的std::function
    std::function()是C++标准库中的一个通用多态函数包装器,它可以存储,复制和调用任意可调用目标(函数,lambda表达式,绑定表达式或其他函数对象).std::function占用固定尺寸的内存.它允许我们将可调用对象(函数,函数指针,Lambda表达式等)包装成一个对象,使得我们可以像操作其他对......
  • QT C++ 解决调试运行时报 The inferior stopped 错误
    (1)报错信息和报错时调用堆栈SignaIReceived-QtCreatorTheinferiorstoppedbecauseitreceivedasignalfromtheoperatingsystem.Signalname:SIGSEGVSignalmeaning:Segmentationfault报错时调用堆栈停留在voidMyTableView::setModel()函数的该行:报错时调用堆栈......
  • 2024.11.26(周二)
    旅游的出行方式有乘坐飞机旅行、乘火车旅行和自行车游,不同的旅游方式有不同的实现过程,客户可以根据自己的需要选择一种合适的旅行方式。实验要求:1. 画出对应的类图;2. 提交源代码;3. 注意编程规范。  1、类图  2、源代码#include<iostream>usingnamespaces......
  • 2024.11.29(周五)
    #导入必要的库importnumpyasnpfromsklearn.model_selectionimporttrain_test_split,cross_val_score,StratifiedKFoldfromsklearn.linear_modelimportLogisticRegressionfromsklearn.datasetsimportload_irisfromsklearn.metricsimportaccuracy_score,pr......
  • 2024.11.28(周四)
    importpandasaspdimportnumpyasnpfromsklearn.datasetsimportload_irisfromsklearn.ensembleimportRandomForestClassifierfromsklearn.metricsimportprecision_score,recall_score,f1_score,accuracy_scorefromsklearn.model_selectionimporttrain......
  • 2024.11.27(周三)
    importpandasaspdfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimportcross_val_score,cross_validate,StratifiedKFoldfromsklearn.ensembleimportRandomForestClassifierfromsklearn.metricsimportprecision_score,recall_score,......
  • 【C++】程序流程控制(下)
    4.2.3  for循环语句作用:满足循环条件,执行循环条件语法:for(起始表达式;条件表达式;末尾循环体){循环语句;} 示例://用for循环打印数字0-9代码://用for循环打印数字0-9#include<iostream>usingnamespacestd;intmain(){   //打印数字0-9   for(inti......
  • 11月30日,工信部人才交流中心 & CUUG - PGCP/PGCM认证考试完成!
    2024年11月30日,由工业和信息化部人才交流中心与北京神脑资讯技术有限公司共同举办的PostgreSQL管理员岗位能力认证考试(PGCP中级/PGCM高级)完成。​中级PG认证专家-PGCP(PostgreSQLCertifiedProfessional):是对PostgreSQL数据库技术能力的一种认可,达到了专家级别,可以对PostgreSQL......