左值右值, 左值引用右值引用,完美转发

一. 左值和右值

左值: 可以取地址的对象
右值: 不可以取地址的对象

python 复制代码
double x=1.0, y = 2.0;
1;				// 字面量, 不可取地址, 是右值
x + y;			// 表达式返回值, 不可取地址, 是右值
max(x, y);		// 传值返回函数的返回值 (非引用返回)

总结就是: 根据是否可以取地址来区分是左值还是右值

二.左值引用和右值引用

左值引用: 对左值的引用, 给左值起别名, 主要是为了避免对象拷贝.

python 复制代码
int a = 1;
int& la = a;

右值引用: 对右值的引用, 给右值起别名, 主要是为了延长对象的生命周期

python 复制代码
int&& ra = 10;

注意, 右值引用变量, 其实是左值, 可以对它取地址和赋值

python 复制代码
int x = 1, y = 2;
int&& right_ref = x + y;
cout << right_ref << endl;
right_ref = 10;
cout << right_ref << endl;

2.1 左值引用指向右值 和 右值引用指向左值

左值引用可以指向右值, 需要const来修饰, 这一点在方法里很常用, 比如push_back接口, 并且const修是, 所以即使传进来的是右值, 也没关系.

python 复制代码
void test(const int& a)
{
	cout << a << endl;
}


int main() {
	int a = 1;
	int&& ra = move(a);
	test(a);
	test(ra);
}

右值引用可以指向左值, 需要std::move(v)

python 复制代码
int a = 1;
int&& ra = move(a);
cout << ra << endl;
a = 2;
cout << ra << endl;
ra = 3;
cout << a << endl;

三. 左值引用的意义

传参返回值时, 避免对象拷贝, 节省了内存, 提搞了效率

python 复制代码
class A
{
public:
	int _a;
	A(int a) : _a(a) {};
	// 返回值时, 避免拷贝
	A& operator +=(const int& i)
	{
		_a += i;
		return *this;
	}
};

// 传参时, 避免拷贝
void test(const A& a)
{
	cout << a._a << endl;
}


int main() {
	A a(1);
	test(a);
	a += 1;
	test(a);
}

但是在返回值返回左值引用时, 如果返回的是局部变量, 那么除了函数作用域是不行的

python 复制代码
class A
{
public:
	int _a;
	A(int a) : _a(a) {};
	A& operator +=(const int& i)
	{
		A a(_a);
		// 返回局部变量, 会直接报错
		return *a;
	}
};

int main() {
	A a(1);
	a += 1;
}

四. 右值引用的意义

将一个对象中的资源移动到另一个对象(资源控制权的转移)。
拷贝构造: const左值引用
移动构造: 右值引用
移动赋值: const左值引用, 是运算符重载

用例如下

python 复制代码
class A
{
public:
	int _a;
	A(int a) : _a(a) 
	{
		cout << "构造函数" << endl;
	};
	A(const A& a)
	{
		cout << "拷贝构造函数" << endl;
		_a = a._a;
	}
	A(A&& a) noexcept
	{
		cout << "移动构造函数" << endl;
		_a = a._a;
	}
	A& operator=(const A& a) noexcept
	{
		cout << "移动赋值函数" << endl;
		_a = a._a;
		return *this;
	}
};

A test()
{
	A a(1);
	cout << &a << endl;
	// 但是现代编译器中, 这里已经不执行移动构造函数了, 用了更好的优化方式
	return a;
}

int main() {
	A a = test();
	cout << &a << endl;

	A a2 = a;
	A a3 = std::move(a);
	A a4(1);
	a4 = std::move(a3);
}

执行结果

五. 完美转发

5.1 前提知识

函数模板中的&&不表示右值引用, 而表示万能引用

不完美转发的例子

python 复制代码
void f(int& x)
{
	cout << "左值引用" << endl;
}

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

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

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

template<typename T>
void test(T&& t)
{
	f(t);
}

int main() {
	int a = 1;
	test(a);
	const int b = 1;
	test(b);

	test(1);
	const int d = 1;
	test(std::move(d));
}

执行结果和我们猜想的差别很大, 是因为, 右值引用本身是左值, 所以在模板函数中, 我们传进去的是右值, 但是等到调用f函数的时候, 已经全部是左值了

5.2完美转发

由上面的例子我们可以看到, 是一个不完美转发, 因为右值失去了它的右值属性, 于是便提出了完美转发, 核心方法是
std::forward, 它在传参过程中, 保留对象原生类型的属性, 我们稍加修改一下上面的例子

python 复制代码
void f(int& x)
{
	cout << "左值引用" << endl;
}

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

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

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

template<typename T>
void test(T&& t)
{
	f(std::forward<T>(t));
}

int main() {
	int a = 1;
	test(a);
	const int b = 1;
	test(b);

	test(1);
	const int d = 1;
	test(std::move(d));
}

执行结果和我们预想的一致了

总结

右值引用是c++11引入的最重要的新特性之一, 配合着移动语义和完美转发, 是的c++程序运行更加高效.

相关推荐
Dontla几秒前
Makefile介绍(Makefile教程)(C/C++编译构建、自动化构建工具)
c语言·c++·自动化
何妨重温wdys36 分钟前
矩阵链相乘的最少乘法次数(动态规划解法)
c++·算法·矩阵·动态规划
重启的码农38 分钟前
ggml 介绍 (6) 后端 (ggml_backend)
c++·人工智能·神经网络
重启的码农38 分钟前
ggml介绍 (7)后端缓冲区 (ggml_backend_buffer)
c++·人工智能·神经网络
雨落倾城夏未凉1 小时前
5.通过拷贝构造函数复制一个对象,假如对象的成员中有个指针类型的变量,如何避免拷贝出来的副本中的该成员之下行同一块内存(等价于默认拷贝构造函数有没有缺点)
c++·后端
雨落倾城夏未凉1 小时前
4.深拷贝VS浅拷贝
c++·后端
tanyongxi662 小时前
C++ 特殊类设计与单例模式解析
java·开发语言·数据结构·c++·算法·单例模式
fqbqrr2 小时前
2508C++,支持rdma通信的高性能rpc库
c++·rpc
liulilittle2 小时前
BFS寻路算法解析与实现
开发语言·c++·算法·宽度优先·寻路算法·寻路
喜欢吃燃面3 小时前
C++算法竞赛:位运算
开发语言·c++·学习·算法