【C++11】右值引用

发布于:2024-04-29 ⋅ 阅读:(39) ⋅ 点赞:(0)

目录

概念

左值右值区分

左值引用和右值引用

 右值引用的价值

额外知识:编译器优化

右值引用减少拷贝

左值引用可以给右值取别名


概念

左值

左值是一个具有内存位置并且可以被取地址的表达式。

右值

右值是一个没有内存位置或临时性的表达式

简单来说,可以取地址的就是左值,不可以取地址的就是右值

左值右值区分

下面都是左值:

 下面都是右值:

 总结:

1、左值引用通常是具有持久性的对象或者变量。例如:变量、数组元素、引用等都是左值。左值具有名字,可以被多次引用。

2、右值通常是临时创建的、没有名字的对象或值。右值只能出现在赋值运算符右边,不能被取地址。例如,字面量、临时对象、函数返回值、表达式等都是右值。右值在表达式中通常是一次性的,且不能被修改。、

左值引用和右值引用

左值引用就是给左值取别名,右值引用就是给右值取别名。

左值引用使用的符号是&

右值引用使用的符号是&&

下面是左值引用:

 下面是右值引用:

 右值引用的价值

额外知识:编译器优化

观察上面的函数,我们要返回s,并且在main函数中拿s接受它。

我们知道,在f()函数中的s的生命周期只在函数里面,而想要被main函数中的s接收到,就只能返回拷贝。而如果返回的内容比较大,那么拷贝的内容就大,效率就会大大降低。

上面这种情况,在编译器视角是怎么进行的呢?

f()函数中的s将内容拷贝一份,生成临时变量,临时变量将内容内容再拷贝给main()函数中的s。拷贝了两次。

编译器可能会存在优化:当代码写在同一行的时候,并且存在两次拷贝构造,编译器就会优化成一次拷贝构造。如下图所示。

 为了进一步讲解,我手写了一个自己的string类

namespace zyy
{
	class string
	{
	public:
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}

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

		//拷贝构造
		string(const string& str) //传的引用
			:_str(nullptr)
		{
			cout << "string(const char* str) -- 拷贝构造" << endl;
			string tmp(str._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类进行测试:

1、不写在同一行,不存在编译器优化的情况

 2、写在同一行,编译器优化之后

右值引用减少拷贝

 在string类中加入下面这样的移动构造

看string的实现也可以发现:

 在加入了移动拷贝之后继续使用之前的代码进行测试:

进行对比: 

左值引用可以给右值取别名

大多数文章只是告诉你调用了移动构造,但是却没有说原理,我这里将原理讲清楚

左值引用可以给右值取别名,而右值的别名是什么?就是右值引用!

观察上面,可以明显看到,10和fmin(x,y)都是右值,那么为什么int& c作为左值引用却报错了?

因为,这两个表达式拿给int& c和int& b的时候,返回的是拷贝,产生了临时变量,不是他们本身给int& c或者int& b,而是把临时变量拿给int& c或者int& b。

临时变量具有常属性,所以,普通的左值引用会报错,必须是const左值引用。 

所以,下面这样就不会报错了。

 观察string类中的两个拷贝构造函数

移动构造 和 移动赋值

继续说回移动构造、移动赋值

 内置类型的右值叫做纯右值

自定义类型的右值叫做将亡值

 移动构造和移动拷贝的本质是什么?

是将 将亡值 的内存转换给新的要来接收的值

 std::move()

move()的作用就是将左值转换成右值。

调用pb(move(pa))的时候,就会出现pb指向了pa的位置。不过这种情况一般都不怎么常用

常用的是s2(move(s1)),这样,s2获得了s1的内容,但是却没有发生拷贝。

完美转发

先看下面的情况:

 那么,如果我们想要调用右值引用就是右值引用呢?该怎么办?

 但是这样很麻烦,首先,不能左值和右值一直调用。其次,会改变右值引用本来的左值属性。

而完美转发,就是来解决这种问题的!

完美转发是指在C++中通过保持传递参数的值类别(左值或右值)和常量性,将参数传递到另一个函数或对象的能力

这样理解太抽象:

 在C++11之后,引入了右值引用和std::forward模板函数,使得实现完美转发变得更加容易。通过使用std::forward函数,可以在传递参数时保持其原始值类别和常量性,从而实现完美转发。

 使用完美转发:


网站公告

今日签到

点亮在社区的每一天
去签到