返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++11新特性之右值引用与完美转发详解
  • 358
分享到

C++11新特性之右值引用与完美转发详解

2024-04-02 19:04:59 358人浏览 八月长安
摘要

目录一、左值与右值二、左值引用与右值引用三、右值引用应用1.移动构造与移动赋值1.模拟实现的string2.移动构造3.移动赋值四、默认移动构造和移动赋值重载函数五、完美转发1.万能

一、左值与右值

顾名思义,左值就是只能放在等号左边的值,右值是只能放在等号右边的值。

c++Prime一书中,对左值和右值的划分为,左值是一个表示数据的表达式,右值是一个即将销毁的值(通常称为将亡值)。比如我们定义的一个变量就是一个左值,而字面常量,表达式返回值,传值返回函数的返回值就是右值。

	10;//右值
	int a = 10;//a是左值
	add(2, 3);//右值
	x+y;//右值
	const int a;//左值

注意,const类型的变量是不能放在等号左侧来为它赋值的,但是他是一个左值。

这里给出一个区分两者的方式:可以取地址的就是左值,不能取地址的就是右值!

二、左值引用与右值引用

我们之前所写的引用都是左值引用符号是&,左值引用的底层是使用指针,它的作用是为对象取一个别名。

而右值引用就是给右值取别名,它的符号是&&,右值引用开辟了空间,得到的一个对象是左值。

	int a = 10;
	int& d = a;//左值引用
	int&& e = 10;//右值引用
	int&& f = a + 1;
	int&& c = add(2, 3);

左值引用不能给右值取别名,右值引用也不能给左值取别名。但是如果对左值进行move(),对左值引用加上const是可以这样进行的。

move的意思就是保证除了赋值和销毁之外,不再使用该左值,即将a的属性转移到了e中,对左值move后是一共右值。

	int&& c = a;//右值引用不能给左值取别名
	int& d = add(3, 4);//左值引用不能给右值取别名
	int&& e = move(a);//当对左值加move的时候可以
	const int& f = add(3, 4);//当对引用加const后可以取别名

同时右值引用不像左值引用一样具有传递性:

	int&& a = 10;
	a=20;
	cout<<&a<<endl;
	//int&& b = a;//错误

这是因为a是一个左值,我们可以打印a的地址,右值经过引用后得到的对象是一个左值。因此我们是可以对a进行赋值的。

三、右值引用应用

1.移动构造与移动赋值

移动构造与移动赋值在C++11中已经加入了STL容器的函数中:

string(string&& str) //移动构造
string& operator=(string&& str)//移动赋值

移动构造与移动赋值都是向函数中传入右值引用,它们的本质与右值基本相同,就是将一个将亡值的数据转移给另一个值。

我们可以在函数string中模拟实现一下移动构造和移动赋值,它们的本质就是调用swap函数完成赋值,而不是使用strcpy创建一个新对象。

1.模拟实现的string

为了方便观察,我们使用自己模拟实现的string来进行说明:

namespace my_string
{
	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);
		}
		// 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)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 资源转移" << endl;
			this->swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 转移资源" << endl;
			swap(s);
			return *this;
		}
		//赋值
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		~string()
		{
			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;
		}
		string operator+(char ch)
		{
			string tmp(*this);
			push_back(ch);

			return tmp;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	my_string::string to_string(int value)
	{
		my_string::string str;
		while (value)
		{
			int val = value % 10;
			str += ('0' + val);
			value /= 10;
		}
		reverse(str.begin(), str.end());
		return str;
	}
}

2.移动构造

当我们调用to_string的时候:

my_string::string ret = my_string::to_string(1234);

当我们不添加移动构造的时候,可以发现最终进行的是一次深拷贝和一次浅拷贝:

这里发现只调用了一次拷贝构造,这是因为编译器做了优化,如果不优化的话,str拷贝构造临时对象,然后临时对象作为to_string的返回值再拷贝构造给ret。其实是发生了两次拷贝构造。

但是编译器做了优化之后,在to_string函数快结束时,返回前直接用str构造ret。

当我们加入拷贝构造之后,会发现只发生了一次移动构造就可以了:

其实在这一过程中编译器也做了优化,str先拷贝构造形成一个临时对象,再由临时对象进行移动构造赋值给ret。

编译器做了优化之后,将str直接当成左值(相当于move了一下),然后进行移动构造生成ret。

通过观察打印结果可以发现,显然移动构造没有再开辟空间,而是直接将数据进行转移,节省了空间,由临时变量进行拷贝构造给ret还会创建一个新的对象,消耗空间。

3.移动赋值

	my_string::string ret;
	ret = my_string::to_string(1234);//调用移动赋值

当不使用移动赋值的时候,以上代码是两段深拷贝实现的:

首先str会调用移动构造,生成临时对象,然后临时对象再调用赋值拷贝构造(深拷贝),定义ret。

当引入移动赋值之后,这个过程就变成了str调用移动构造生成临时对象,临时对象再通过移动运算符重载生成ret,整个过程中没有一次深拷贝。

C++11中,所有STL容器中,都会提供一个右值引用的版本。

四、默认移动构造和移动赋值重载函数

与六大成员函数一样,编译器在一定的条件下,也会生成自己的默认移动构造函数,只不过生成的条件更加复杂:

1.如果你自己没有实现移动构造函数,并且没有实现析构函数,拷贝构造,拷贝赋值构造中的任意一个。那么编译器会自动生成一个默认构造函数。默认生成的移动构造函数,对内置类型进行直接拷贝,对于自定义类型,如果有对应的移动构造函数就调用其对应的移动构造函数,如果没有那么调用拷贝构造。

2.如果你没自己实现移动赋值重载函数,且没有实现析构函数,拷贝构造,拷贝赋值重载中的任何一个,编译器会自动生成一个移动赋值重载函数。默认生成的移动赋值重载函数,对内置类型直接进行赋值,对于自定义类型,如果有对应的移动赋值重载函数就调用其对应的移动赋值重载函数,如果没有则调用拷贝赋值。

3.如果你提供了移动赋值构造或者移动赋值重载函数,那么编译器就不会自动生成。

五、完美转发

1.万能引用

在模板中,&&表示的不是右值引用,而是万能引用,即既可以接收左值,又可以接收右值。

void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}

此时传入的t既可以是左值,也可以是右值。

2.完美转发

运行以下程序,发现最终识别的都是左值引用。

void Func(int&& x)
{
	cout << "rvalue" << endl;
}
void Func(int& x)
{
	cout << "lvalue" << endl;
}
template<class T>
void PerfectForward(T&& t)
{
	Func(t);
}
int main()
{
	PerfectForward(10);//左值
	int a;
	PerfectForward(a);//左值
	PerfectForward(move(a));//左值
}	

这是因为右值引用一旦引用了,就变成了左值,如果我们还希望保持该右值引用的特性的话,需要使用forward函数来对其进行封装:

	Func(forward<T>(t));

forward(t)来进行封装的意义在于,保持t原来的属性,如果它原来是左值那么封装之后还是左值,如果它是右值的引用,则将其还原成右值。该函数的作用称为完美转发,由于这一性质,STL容器的插入也可以使用右值引用来实现。

即支持:

vector<int> v;v.push_back(111);

在该右值引用版本的插入中,调用的就是forward(val)。

到此这篇关于C++11新特性之右值引用与完美转发详解的文章就介绍到这了,更多相关C++右值引用 完美转发内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++11新特性之右值引用与完美转发详解

本文链接: https://lsjlt.com/news/168293.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++11新特性之右值引用与完美转发详解
    目录一、左值与右值二、左值引用与右值引用三、右值引用应用1.移动构造与移动赋值1.模拟实现的string2.移动构造3.移动赋值四、默认移动构造和移动赋值重载函数五、完美转发1.万能...
    99+
    2024-04-02
  • C++左值与右值,右值引用,移动语义与完美转发详解
    目录C++——左值与右值、右值引用、移动语义与完美转发一、左值和右值的定义二、如何判断一个表达式是左值还是右值(大多数场景)三、C++右值引用四、std::m...
    99+
    2024-04-02
  • C++精要分析右值引用与完美转发的应用
    目录区分左值与右值右值引用移动语义完美转发结语区分左值与右值 在C++面试的时候,有一个看起来似乎挺简单的问题,却总可以挖出坑来,就是问:“如何区分左值与右值?&rdqu...
    99+
    2024-04-02
  • C++右值引用,移动语义与完美转发得方法
    本篇内容主要讲解“C++右值引用,移动语义与完美转发得方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++右值引用,移动语义与完美转发得方法”吧!C++&mdash;&mda...
    99+
    2023-06-29
  • C++11学习之右值引用和移动语义详解
    目录左值引用与右值引用1、左值与右值2、纯右值、将亡值3、左值引用与右值引用4、右值引用和 std::move 使用场景引用限定符const 和引用限定符移动语义—std...
    99+
    2023-02-23
    C++11右值引用 移动语义 C++11右值引用 C++11 移动语义
  • C++11语法之右值引用的示例讲解
    目录一、{}的扩展initializer_list的讲解:二、C++11一些小的更新decltypenullptr范围for新容器三、右值引用右值真正的用法完美转发默认成员函数总结一...
    99+
    2024-04-02
  • C#11新特性使用案例详解
    目录前言新特性之原始字符串使用案例原始字符串使用需要注意的地方什么情况下需要超过三个双引号开头尾引号和尾引号前面的换行符不包括在最终内容中结尾的三个引号不另起一行行不行和内插字符一起...
    99+
    2024-04-02
  • C++11新特性之变长参数模板详解
    目录C++11 变长参数模板变长函数参数包如何解参数包sizeof()获得函数参数个数递归模板函数变参模板展开结论C++11 变长参数模板 在C++11之前,无论是类模板 还是函数...
    99+
    2024-04-02
  • C++ 右值引用与 const 关键字详解
    C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,const关键字是一种修饰符。修饰符本身,并不产生任何实际代码。就 const 修饰符而言,它用来告...
    99+
    2024-04-02
  • C++11新特性之随机数库(Random Number Library)详解
    目录从前的随机数随机数库(Random Number Library)随机数引擎随机数分布类生成平均分布的整数生成平均分布的实数生成正态分布的实数生成概率可控的布尔值补充:真正的随机...
    99+
    2024-04-02
  • JDK1.8新特性之方法引用 ::和Optional详解
    一:简介 方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式 静态方法引用,通过类名::静态方法名, 如 Integer::pa...
    99+
    2024-04-02
  • C++右值引用与移动构造函数基础与应用详解
    目录1.右值引用1.1左值右值的纯右值将亡值右值1.2右值引用和左值引用2.移动构造函数2.1完美的移动转发1.右值引用 右值引用是 C++11 引入的与 Lambda 表达式齐名的...
    99+
    2023-02-13
    C++右值引用 C++移动构造函数
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作