左值、右值、左值引用和右值引用

左值、右值和左值引用,在C++11之前,我们都很熟悉也都很好理解。左值(LValue)就是有名字能够寻址的对象的值类型,右值就是在内存上没有名字的数值的值类型,左值引用就是指向左值的引用。

C++11引入了右值引用,从而可以去读写在内存上临时的右值(C++11之前可以使用const左值引用去指向右值来读右值的内容,但是无法修改所指向的右值内容)。

C++11之前,任何对象不是左值就是右值,那引入右值引用之后,右值引用到底是左值还是右值呢?考虑到右值引用在行为使用上与右值有相似的地方,但也有冲突的地方,具体细节可以参考C++标准文档(n3055)。于是C++标准委员会将原来非黑即白的左值和右值两种类型细分成了5种类别,其中一些类别还会有共同重叠的部分。这5种类别中最值的关注的就是新创建了XValue,C++标准规定将一个右值引用绑定到一个对象,这个行为或者说是这个表达式的结果就是XValue。可见,XValue的诞生就是为右值引用量身定制的。那XValue这个名字起源于eXpiring这个单词,其实也很好理解,当我们使用一个右值引用想要move一个临时对象的值时,这个临时对象的生命周期也就即将结束。

除了XValue,另外4中类型分别是LValue, GLValue,RValue和PRValue。LValue还是传统意义上的左值。GLValue是generalized LValue,它包括LValue和XValue。PRValue是pure RValue,虽然是新的类型,但它就是之前传统意义上的右值。RValue现在是一个统称,它包括PRValue和XValue。具体包含关系可以见下图:

需要注意的是,XValue它既是GLValue,也是RValue,所以这里是的关系。 

从图中可以看出,C++11之后,任何一个值,它肯定是LValue,XValue和PRValue中的一种。LValue和PRValue,我们都很好理解,那XValue到底会在哪些情况下出现用到呢?

C++标准规定了以下这些情况下的表达式的结果都是XValue:

  • 无论是显式还是隐式调用一个函数,只要函数返回值的类型是指向对象的右值引用,那么调用的结果就是XValue;
  • 将现有的对象转换成指向对象的右值引用,这个行为或者叫表达式的结果就是XValue;
  • 如果一个对象本身已经是XValue,那访问这个对象的非static的成员变量的表达式的结果就是XValue;
  • 如果一个对象本身已经是XValue,并且通过.*pointer-to-member的方式来访问成员变量,那这个表达式的结果就是XValue

总而言之,以上这些规则表明,有名字的(named)右值引用将会被视为左值没名字的(unnamed)右值引用将会被视为XValue。下面是这些规则的一个例子:

struct A {
    int m;
};

A&& operator+(A, A);
A&& f();

A a;
A&& ar = static_cast<A&&>(a);

表达式f(),表达式f().m,表达式static_cast<A&&>(a),表达式 a + a,以上这些表达式的结果都是XValue。表达式ar是一个左值,它的类型是A。根据文档中的解释,如果一个表达式是一个指向类型T的引用,那么这个表达式的类型将会调整为类型T。

上面的例子中static_cast<A&&>(a)是XValue,而std::move的实现正是使用了static_cast<X&&>:

template<typename T> struct remove_reference { typedef T type; };
template<typename T> struct remove_reference<T&> { typedef T type; };
template<typename T> struct remove_reference<T&&> { typedef T type; };

template<typename T>
constexpr typename remove_reference<T>::type && move(T && arg) noexcept
{
  return static_cast<typename remove_reference<T>::type &&>(arg);
}

所以,std::move的返回值是一个XValue,而XValue也是一个右值,所以std::move的返回值也是一个右值,这也是为什么使用std::move可以触发调用move constructor/assignment,而不是调用copy constructor/assignment:

struct X
{
  std::string s_;

  X(){}

  X(const X & other) : s_{ other.s_ } {}

  X(X && other) noexcept : s_{ std::move(other.s_) } {}
  // other is an lvalue, and other.s_ is an lvalue too
  // use std::move to force using the move constructor for s_
  // don't use other.s_ after std::move (other than to destruct)
};

int main()
{
  X a;

  X b = std::move(a);
  // a is an lvalue
  // use std::move to convert to a rvalue,
  // xvalue to be precise,
  // so that the move constructor for X is used
  // don't use a after std::move (other than to destruct)
}

参考资料:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3055.pdf

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值