简单探讨右值引用与左值引用

一:什么是左值,是什么是右值

  • 左值:顾名思义,左值就是可以放在等号左边的值具名(具备名称)、能够取地址。如:变量名、返回左值引用的函数调用、前置自增\自减、赋值运算或符号赋值运算、解引用等等。
  • 右值:和左值相反,只能在等号右边不具名不能取地址
    • 纯右值:字面值(int i= 1)、返回非引用类型的函数调用、后置自增\自减、算术表达式、逻辑表达式、比较表达式。
    • 将亡值:c++11新引入的与右值引用(移动语义)相关的值类型。将亡值用来触发移动构造或移动赋值构造,并进行资源转移,之后将调用析构函数。
区分表达式的左右值属性:如果可对表达式用 & 符取址,则为左值,否则为右值。

二:左值引用和右值引用

形式定义:

//左值引用:type &引用名 = 左值表达式
int i = 42;
int &r = i;//正确,左值引用
int &r1 = i * 42; //错误, i*42是一个右值
const int &r = i*42;//正确,可以将一个const的引用绑定到一个右值上

//右值引用:type &&引用名 = 右值表达式
int i = 42;
int  &&r = i;  //错误,i是一个变量,变量都是左值
int &&r = i *42;  //正确,i*42是一个右值

区别:

  1. 左值引用就是对左值的引用,右值引用就是对右值的引用;声明出来的左值引用和右值引用都是左值,因为引用本身就是一个变量。
  2. 功能差异
  •  左值引用:左值引用是为了避免对象的拷贝;
  • 右值引用:右值引用时为了实现移动语义和完美转发。

三:移动语义和完美转发

移动语义

        在对象赋值时,避免资源的重新分配;通过出发移动构造来实现移动语义(基于move)。以移动而非深拷贝的方式初始化含有指针成员的类对象。移动语义值将其他对象(通常是临时对象)拥有的内存资源移位己有。

class A
{
public:
	A() {
		p = new int(10);
	};
	//拷贝构造  深拷贝
	A(const A &a) {
		//这里来尝试实现移动语义
		//p = a.p;
		//因为a是const,所以这里不成功,之所以要赋值为空,防止析构时p成为野指针
		//a.p = nullptr; 

		p = new int(10);
		memccpy(p, a.p, 10 * sizeof(int));
	};
	~A() {
		if (p!= nullptr)
		{
			delete[]p;
			p = nullptr;
		}
	};
	//移动拷贝构造
	A(A &&a) {//这里传入的值就是将亡值
		this->p = a.p;
		a.p = nullptr;
	};

	int *p;
};

int main()
{
    A a;
    //使用移动构造
    A b(std::move(a));
    return 0;
}

完美转发

        完美转发基于万能引用,引用折叠以及std::forward模板函数。

       定义:函数模板可以将自己的参数完美的转发给内部调用的其他函数;完美转发指不仅能准确的转发参数的值,汉能保证被转发的参数的左右值属性不变。

        借用万能引用,通过引用的方式接收左右值属性的值。

void func(int &n)
{
	//...
}
void func(int &&n)
{
	//...
}

template<typename T>
void revoke(T &&t)
{
	func(forward<T>(t));
}

万能引用

        谈及万能引用得谈谈“&&”的特性了;

“&&”特性
        右值引用就是对一个右值进行引用的类型。因为右值没有名字,所以我们只能通过引用的方式找到它。
       无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所把绑定对象的内 存,只是该对象的一个别名。
      通过右值引用的声明,该右值又“ 重获新生 ,其生命周期其生命周期与右值引用类型变量的生命周期一 样,只要该变量还活着,该右值临时量将会一直存活下去。
&& 的总结如下:
  1. 左值和右值是独立于它们的类型的,右值引用类型可能是左值也可能是右值。
  2. auto&& 或函数参数类型自动推导的 T&& 是一个未定的引用类型,被称为 universal references-万能引用。它可能是左值引用也可能是右值引用类型,取决于初始化的值类型。
  3. 所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用折叠都为左值引 用。当 T&& 为 模板参数时,输入左值,它会变成左值引用,而输入右值时则变为具名的右 值引用。
  4. 编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值。

引用折叠

        参数为左值或左值引用,万能引用T&&将转换为左值引用int&;为右值或右值引用,将转换为右值引用int &&;

  1. 所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
  2. 所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)

引用折叠解决参数接收的问题。

template<typename T>
void revoke(T &&t)
{
	func(forward<T>(t));
}

revoke(static_cast<int &>n);
//编译器会将int & &&t折叠为int &t;
revoke(static_cast<int &&>n);
//编译器会将int && &&t折叠为int &&t;
revoke(n);
 //编译器会将int &&t折叠为int &t;   

//解决透传问题
forward<T>v;
//T为左值引用,v将转换为T类型的左值;T为右值引用,v将转换为T类型的右值;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值