栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++11右值引用

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++11右值引用

文章目录
  • 左值和右值
  • 左值,右值与引用的关系
  • 右值的特殊点
  • 左值引用的应用场景和短板
  • 移动语义
  • 关于move
  • 完美转发(&& 和 forward)
  • 总结

左值和右值

一般没有明确的定义。
左值有以下性质:

  1. 左值可以取地址
  2. 左值可以修改(除了const的左值)
  3. 左值可以放在等号左边,也可以放在等号右边

右值有以下性质:

  1. 右值不可以取地址
  2. 右值不可以修改
  3. 右值只能放在等号右边

最重要的性质是左值可以取地址,右值不可以取地址。

常量,表达式的返回值,非左值的函数返回值是右值

如下:

10
x + y;
f(10);

可以参考下图的定义:比较好理解和记忆

C++11对右值进行了严格的区分:
1.C语言中的纯右值,比如:a+b, 100
2.将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

总结一下, 知道以下几点就足够了
1.右值分为纯右值和将亡值(中间状态)
2.左值可以取地址,右值不可以取地址。

左值,右值与引用的关系


下面代码对4种情况都举了例子:

int main()
{
	//左值引用左值
	int a;
	int& b = a;

	//const的左值引用右值
	const int& c = 10;
	//const的左值引用左值
	const int& f = a;

	//右值引用右值
	//int&& d = a;  右值引用左值是非法的,权限扩大了,右值没有地址
	int&& d = 10;

	//右值引用move后的左值
	int&& e = move(a);//move相当于把左值变成右值了
}
右值的特殊点

右值是不能被修改的,10 = 20这种操作是不允许的。
但是加了右值引用之后,右值可以被修改。并且右值引用是可以取地址的。效果如下:

int&& d = 10;
d = 20;
cout << &d;


可能会有这种想法,那么加了右值引用的右值不就变得和左值一样了。
答案确实是这样的,后面会讲一个叫完美转换的东西,就和这个性质有关系。

左值引用的应用场景和短板

左值引用一般都是用来做函数传参和函数返回值。这样可以减少拷贝次数,提高效率。

但是有一种情况,左值引用没有办法减少拷贝次数。
要返回局部对象的时候,没有办法将函数返回值变成左值引用。 必须拷贝。如果不拷贝,就相当于访问已经析构了的空间了。

右值引用的其中一个应用场景就在这里
ps:并不是说把返回值变成右值引用,右值引用不会做函数返回值!!!

移动语义

移动语义又分为了移动构造和移动赋值。在C++11里面,6个默认成员函数已经变到8个了,加多了移动构造函数和移动赋值函数。

移动语义需要使用右值引用。

测试代码:如果没有写移动构造的时候,operator+返回的局部对象调用的是深拷贝来返回的。

#include 

using namespace std;
class String
{
public:
	String(char* str = (char*)"")
	{
		if (nullptr == str)
			str = (char*)"";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		cout << "String(const String& s)" << ' ' << "深拷贝" << endl;
		strcpy(_str, s._str);
	}

	void swap(String& s)
	{
		std::swap(_str, s._str);
	}

	

	String& operator=(String&& s)
	{
		cout << "String& operator=(String&& s) " << "移动语义" << endl;
		_str = (char*)"";
		swap(s);
	}

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			cout << "String& operator=(const String& s) " << "深拷贝" << endl;
			char* pTemp = new char[strlen(s._str) + 1];
			strcpy(pTemp, s._str);
			delete[] _str;
			_str = pTemp;
		}
		return *this;
	}

	String operator+(const String& s)
	{
		char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
		strcpy(pTemp, _str);
		strcpy(pTemp + strlen(_str), s._str);
		String strRet(pTemp);
		return strRet;
	}

	~String()
	{
		if (_str) delete[] _str;
	}
private:
	char* _str;
};

int main()
{
	String s1((char*)"hello");
	String s2((char*)"world");
	String s3(s1 + s2);
}

原理如下图:

-----------------------------------手动分割线----------------------------------------
上面讲的是没有移动构造函数情况下:现在我们加上移动构造函数。

移动构造函数写法:
1.参数是右值引用
2.交换当前对象和右值引用的所有资源

移动构造和拷贝构造的区别在于:拷贝构造要多开一块空间,移动构造不需要。


移动这个词很形象,就是把资源不断的移动到最终的对象上

String(String&& s)
{
	cout << "String(String&& s) " << "移动语义" << endl;
	_str = nullptr;
	swap(s);
}

再次运行

原理:


总结一下:

关于move

move就是把左值变成右值的一个函数。它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
也就是说:只要你不调用移动构造或者移动赋值,左值的值就不会被移动。

一旦发生了移动拷贝,a的字符串就没有了。

完美转发(&& 和 forward)

完美转发是指一个模板函数,接收到不同的引用参数,可以调用不同的函数去执行。

void f(int&& t)
{
	cout << "右值引用" << endl;
}

void f(int& t)
{
	cout << "左值引用" << endl;
}

void f(const int& t)
{
	cout << "const左值引用" << endl;
}

template
void perfectForward(T&& t)
{
	//f(forward(t));
	f(t);
}

int main()
{
	int&& a = 10;
	int& b = a;
	const int& c = a;
	perfectForward(10);
	perfectForward(b);
	perfectForward(c);
}

要说两点:

template
void perfectForward(T&& t)

上面代码里面的模板T&& t并不是说接收的是右值引用,在模板后面加上**&&代表的是万能引用,既能接收左值引用,也可以接收右值引用。**


必须要加上forward< T >(t)这个函数。我们上面说过,右值在被引用的瞬间就变成左值了,如果不加forward,右值引用在继续传参的时候右值引用的类型就会变成左值引用

因此只要用右值引用传参,就一定要用forward< T >,否则传的就是左值引用。

总结

C++11中右值引用主要有以下作用:

  1. 实现移动语义(移动构造与移动赋值)
  2. 给中间临时变量取别名:(string&& s = s1 + s2,表达式的返回值也是右值)
  3. 实现完美转发
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/853329.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号