C++11左值和右值、左值引用和右值引用浅析

从字面意思来讲,左值就是“能用在赋值语句等号左侧的内容(它得代表一个地址)”;右值就是不能作为左值的值,即右值不能出现在赋值语句中等号的左侧。C++中的一条表达式,要么就是右值,要么就是左值,不可能两者都是。但是一个左值有时候又能被当做右值使用(即这个对象具有右值属性,但并不是右值)。如 i = i+1这条语句:对象i在赋值语句等号的右侧时,用的是这个对象的值(具有右值属性),这个对象在赋值语句左侧时,用的是对象在内存中的地址(具有左值属性)。所以一个对象可以同时具有左值和右值属性^{[1]}

赋值运算符左侧的对象就是一个左值,但实质上整个赋值语句的结果仍然是左值,只不过在使用printf函数或者cout等场景进行输出的时候被当做右值使用(即左值具有右值属性)。

在左值表达式和右值表达式中,不要被“表达式”三个字所迷惑,如一个变量也可以称之为表达式、因此可以这样理解,左值表达式就是左值,右值表达式就是右值^{[1]}

左值引用(绑定到左值):引用那些希望改变值的对象,左值引用带一个"&"。

右值引用(绑定到右值):首先它也是一个引用,但是右值引用所侧重表达的意思往往是表示所引用对象的值在使用之后就无需保留了(如临时变量),右值引用带有两个“&&”。

左值引用就是绑定到左值的引用,右值引用就是绑定到右值的引用。一般来讲,右值引用其实主要是用来绑定到那些“即将销毁/临时的对象”上。右值引用也是引用。并且,右值引用虽然绑定到了右值对象上,但是右值引用本身还是个左值,毕竟右值引用也是位于等号左边的,如下代码是可以正常编译通过的。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int i = 1;
    int &&r1 = i++; // r1虽然是个右值引用,引用到了右值对象,但是r1本身还是个左值
    int &r5 = r1;   // 可以,说明r1本身是一个左值
    cout << r5 << endl;

    return 0;
}

返回左值引用的函数,连同赋值、下标、解引用和前置递增递减运算符等等,都是返回左值表达式(左值)的例子,因此可以讲一个左值引用绑定到这类表达式的结果上。

返回非引用类型的函数,连同算术、关系、位以及后置递增运算符,都生成右值,不能将一个左值引用绑定到这类表达式上,但是可以将一个const的左值引用或者一个右值引用绑定到这类表达式的结果上。

所有变量都要看成是左值,因为他们是有地址的,这类变量用右值绑定也绑定不上。

任何函数里的形参都是左值,即使在诸如“void (int &&)”这种写法里面,形参w的类型是右值引用(需要绑定到右值上),但是w本身还是一个左值。

右值引用引入的目的是提高程序运行的效率问题,,其手段是把复制对象操作变成移动对象操作

移动对象的概念如下:假设对象A不再使用了,那么就可以把对象A里面一些使用new分配的内存卡的所有权转给对象B,对于对象B而言,就不用再new出一些内存块了。把A种new出来的内存块直接转给B后,A 就把指向这些内存卡的指针清空一下。这就叫移动对象(把老对象里面的一些对象转移给了新对象)。也就是说,很多分配出去的内存并没有被回收而是转移给了新对象,这种把某一些内存块从原来的对象A转移给了新对象的操作就叫做移动

对象的移动操作是通过移动构造函数和移动赋值运算符来实现的,移动构造函数和移动赋值运算符的外观看起来与拷贝构造函数和复制运算符非常像,只不过移动构造函数和移动赋值运算符所需要的参数类型是“&&”这种右值引用类型

std::move函数虽然翻译成中文是移动的意思,但是这个函数实际上并没有做任何移动操作,而是把一个左值强制转换成右值(带来的结果就是一个右值引用可以绑定到这个转换得到的右值上面去了)。本来一个右值引用是无法绑定到左值上去的,但是经过move函数处理之后(将左值进行转换得到右值),这个右值引用就能绑定到原来的一个左值上面去了。有些函数的参数是一个右值引用,需要绑定到右值,此时也可以用move函数将左值转换成右值,转换结果就可以当做实参传递给该函数了。

通常情况下,如果需要移动语义,程序员必须自定义移动构造函数。当然对于一些简单的、不包含任何资源的类型来说,实现移动语义与否都无关紧要,因为对于这样的类型而言,移动就是拷贝,拷贝就是移动。移动语义往往构造的是资源型的类型,如智能指针,指针、文件流等(即:移动语义对于拥有资源(如内存、文件句柄)的对象有效,如果是基本类型,使用移动语义没有意义)

对于移动构造函数而言,抛出异常是件危险的事情。因为移动语义还没完成,一个异常却抛出来了,这可能会导致一些指针成为悬挂指针。因此程序员应当尽量编写不抛出异常的移动构造函数:通过为其添加一个noexcept关键字,可以保证移动构造函数抛出的异常会直接调用terminate终止程序运行而不是造成指针悬挂的问题^{[2]}

[1]《C++新经典》,王建伟编著

[2]《深入理解C++11:C++11新特性解析与应用》,IBM XL编译器开中国开发团队著

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值