C++ 右值引用与左值引用

意义:可以避免无谓的复制,提高程序的性能。

左值:表达式结束后依然存在的持久化对象
右值:表达式结束后不再存在的临时对象

所有的具名变量和对象都是左值,而右值不具名。
区分左值和右值的快捷方法:
看能不能对表达式取地址,如果能则是左值,否则就是右值。

右值分为纯右值和将亡值。
纯右值是C++98中的右值概念,如非引用函数返回的临时变量;
一些运算表达式,如4+6产生的临时变量;不和对象关联的字面量值,
如10,‘s’,true,“hello”等这些不能被取地址的值。

将亡值:c++11中新增的和右值引用相关的表达式,这样的表达式通常是将要移动的对象
T&&函数返回值,std::move()函数的返回值等。
将亡值和纯右值统一看成右值,不影响使用。

c++98中引用很常见,就是给变量取一个别名,在c++11中,因为增加了右值引用的概念,
所以c++98中的引用都称为左值引用。

int a = 10;
int &refA = a; 
int &b = 1;   //编译错误,1是右值,不能使用左值引用

c++11中的右值引用使用&&符号,如

int &&a = 1;
int b = 1;
int &&c = b;  //编译错误,不能将左值赋值给一个右值引用
class A
{
public:
    int a;
};
A getTemp()
{
    return A();
}
A &&a = getTemp();  //getTemp()返回值是右值(临时变量)

getTemp()返回的右值本来在表达式语句结束后,其生命也就该终结了(临时变量),而通过
右值引用,该右值又获得了新生,其生命周期与右值引用类型变量a的生命一样,只要a活着,该
右值临时变量将会一直存活下去,实际上就是给临时变量去了一个名字。

a的类型为右值引用类型(int &&),如果从左值和右值的角度区分它,它实际上是一个左值。
因为可以对它取地址,而且它还有名字,是一个已经命名的右值。

所以,左值引用只能绑定左值,右值引用只能绑定右值。常量左值引用是个特例,它可以算一个
万能的引用类型,可以绑定非常量左值,常量左值,右值,而且在绑定右值的时候,可以像右值引用一样
将右值的生命期延长,缺点是只能读不能改。例子如下:

const int &a = 1;  //常量左值引用绑定右值,不会报错
class A
{
public:
    int a;
};
A getTemp()
{
    return A();
}
const A &a =getTemp();   //不会报错,而A& a会报错

实际上,我们在很多情况下都使用了常量左值引用这个功能,例子如下:

class Copyable
{
public:
    Copyable() {}
    Copyable(const Copyable &o)
    {
        std::cout << "Copied" << std::endl;
    }
};

Copyable ReturnRvalue()
{
    return Copyable(); //返回一个临时对象
}

void AcceptVal(Copyable a)
{
}
void AcceptRef(const Copyable &a)
{
}

int main()
{
    std::cout<<"pass by value"<<std::endl;
    AcceptVal(ReturnRvalue());  //应该调用2次拷贝构造函数
    std::cout<<"pass by reference"<<std::endl;
    AcceptRef(ReturnRvalue());  //应该只调用一次拷贝构造函数
}

上述例子运行之后,结果和预想的不一样。AcceptVal(ReturnRvalue())需要调用两次拷贝构造函数,一次在ReturnRvalue()函数中,构造一个Copyable()对象,返回的时候会调用拷贝构造函数生成一个临时对象。在调用AcceptVal()时,会将这个对象拷贝给函数的局部对象a,一共调用了两次拷贝构造函数。而AcceptRef()的不同之处在于形参是常量左值引用,它能接收一个右值,而不需要拷贝。

实际的结果是,不管哪种方式,一次拷贝构造函数都没有调用。

这是因为编译器开启了返回值优化(RVO/NRVO,RVO,Return Value Optimization返回值优化;NRVO,Nameed Return Valude Optimization)。编译器发现ReturnRvalue内部生成了一个对象,返回之后还需要生成一个临时对象调用拷贝构造函数,很麻烦,所以直接优化成一个对象,避免拷贝,而这个临时变量又被赋值给了函数的形参,还是没必要,这3个变量都用一个变量代替了,不需要调用拷贝构造函数。

为了能够更好的观测结果,可以在编译的时候加上-fno-elide-constructors选项(关闭返回值优化),此时结果和预想的一样。上述的例子是想说明常量左值可以绑定一个右值,可以减少一次拷贝(使用非常量左值引用会使失败,因为ReturnRvalue()返回的是临时对象(右值))。

//g++ test.cpp -o test -fno-elide-constructors

总结:T是一个具体类型

(1)左值引用,使用T&,只能绑定左值

(2)右值引用,使用T&&,只能绑定右值

(3)常量左值,使用conts T&,可以绑定左值和右值。

(4)已命名的右值引用,编译器会认为是左值。

(5)编译器有返回值优化功能,但不可过于依赖。

参考:https://www.jianshu.com/p/d19fc8447eaa

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷小川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值