C++11——右值引用

C++11——右值引用_第1张图片

文章目录

    • 1. 左值和右值
      • 1.1 什么是左值
      • 1.2 什么是右值
    • 2. 左值引用和右值引用
      • 2.1 左值引用的使用场景
      • 2.2 右值引用的使用场景
    • 3.移动语义
    • 4. 完美转发

1. 左值和右值

1.1 什么是左值

左值,不能根据名字来判断,即左边的就是左值,这个是错误的。

int a = 10;	//a是左值 10是右值
int b = a;	//b是左值 a也是左值

左值是一个表示数据的表达式,可以对其取地址,一般可以对它进行赋值。左值既可以出现在赋值符号的左边,又可以在赋值符号的右边

对于const修饰的左值,不能给它赋值,但是可以取地址

1.2 什么是右值

右值也是一个数据的表达式,如:字面常量、表达式返回值等。右值不能出现在赋值符号的左边,且右值不能进行取地址

int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	int a = 10;
	int b = a;
	
	//右值
	10;		
	a + b;	
	Sub(a, b);
	return 0;
}

2. 左值引用和右值引用

在语法上来说,引用就是取别名。那么左值引用就是给左值取别名;右值引用就是给右值取别名

void t1()
{
	int a = 1;
	//左值引用
	int& r1 = a;	//给左值取别名
	const int& r2 = 10;	//加上const修饰,给右值取别名

	//右值引用
	int&& r3 = 10;	//给右值取别名
	int&& r4 = std::move(a);	//加上move给左值取别名
}

2.1 左值引用的使用场景

左值引用可以做参数、做返回值,这样可以减少拷贝。

但是对于局部对象的返回,左值引用无法处理。

string& func()
{
	string str("hello");

	return str;	//函数结束str销毁了
}
int main()
{
	string s = func();
	cout << s << endl;
	return 0;
}

那这样就只能传值返回,如果对象比较大,那么这样的代价就比较大

C++11——右值引用_第2张图片

2.2 右值引用的使用场景

对于内置类型的右值,我们通常称为纯右值;对于自定义类型的右值,我们称为将亡值

而上面的func()就是一个将亡值,那么我们就可以采用移动拷贝,直接拿到这个将亡值的资源

mystring::string func()
{
	mystring::string str("hello");

	return str;	//函数退出时,str销毁
}

int main()
{
	mystring::string s1 = func();
	cout << endl;
	mystring::string s2;
	s2 = func();
	return 0;
}

C++11——右值引用_第3张图片

这里的func()中的返回值str被编译器(C++每个编译器都这样)识别成了右值,如果不这样,那么就得加上move(str),才能匹配重载的右值引用版本,这样就得修改大部分的代码,C++11的性能就难以直接提升;所以直接识别成右值,就能直接匹配上。

例如下面这段代码

vector<vector<int>> func()
{
	vector<vector<int>> vv;
	//...
	//
	//...
	return vv;
}

编译器直接将vv识别成了右值,就极大的减少了拷贝,提升了性能。

3.移动语义

当一个右值引用一个左值时,可以通过move函数将左值转换成右值

C++11——右值引用_第4张图片

这里s1move处理之后,被识别成了右值,然后会调用移动构造,将资源转移给了s3

4. 完美转发

模板中的&&叫做万能引用

如果实参是左值,那么就是左值引用(引用折叠)

如果实参是右值,那么就是右值引用

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

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

int main()
{
	PerfectForward(10);           // 右值
	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b);      // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

这里运行发现,竟然全部都是左值引用

C++11——右值引用_第5张图片

右值,是不能修改的;而右值引用,是可以修改的,而右值引用的本质又是左值

void t2()
{
	//10是右值
	int&& ra = 10;
	//ra是右值引用,本质上是开了一块空间将10的值存起来
	
	//10++;	//error
	//cout << &10 << endl;	//error
	ra++;
	cout << &ra << endl;
}

所以上面代码的t右值引用变量的属性识别成了左值,从而导致全部调用的左值引用函数

如果不识别出左值属性,那么在移动构造的场景下,就不能修改,无法进行资源转移

想要其保持原有的属性,即左值引用保持左值属性,右值引用保持右值属性。我们可以使用forward函数让其保持原有属性

C++11——右值引用_第6张图片
t右值引用变量的属性识别成了左值,从而导致全部调用的左值引用函数

如果不识别出左值属性,那么在移动构造的场景下,就不能修改,无法进行资源转移

想要其保持原有的属性,即左值引用保持左值属性,右值引用保持右值属性。我们可以使用forward函数让其保持原有属性。


那么本次的分享就到这里,我们下期再见,如果还有下期的话。

你可能感兴趣的:(原创,C++,c++,java,开发语言)