0%

右值引用

右值引用

1. 什么是右值引用

右值引用是C++11新加的一种引用类型,是一种仅能绑定到右值上的引用。不同于左值引用仅用一个&表示,右值引用用两个&&表示。

1
2
3
int x{ 5 };
int& lref{ x }; // l-value refrence initialized with l-value x
int&& rref{ 5 } // r-value refrence initialized with r-value 5

右值引用有两个非常有用的性质: 1. 右值引用将初始化他们的对象的寿命延长到右值引用的寿命;2. 非常量的右值引用允许修改右值。

看一个实际例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

class Fraction
{
private:
int m_numerator;
int m_denominator;

public:
Fraction(int numerator = 0, int denominator = 1) :
m_numerator{ numerator }, m_denominator{ denominator }
{
}

friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << '/' << f1.m_denominator;
return out;
}
};

int main()
{
auto &&rref{ Fraction{ 3, 5 } }; // r-value reference to temporary Fraction

// f1 of operator<< binds to the temporary, no copies are created.
std::cout << rref << '\n';

return 0;
} // rref (and the temporary Fraction) goes out of scope here

这段代码打印出:

1
3/5

Fraction{ 3, 5 }是一个匿名对象(临时对象),在这行语句结束的时候就出了作用域,本来应该被销毁掉,但是我们用了一个右值引用来绑定它,因此延长了它的生命期,直到main函数结束,局部变量rref被销毁的时候,这个临时对象才会被销毁。
再看另外一个例子:

1
2
3
4
5
6
7
8
#include<iostream>
int main()
{
int &&ref{ 5 };
rref = 10;
std::cout << rref << std::endl;
return 0;
}

这段代码执行结果是:

1
10

这里用字面值初始化一个右值引用,会创建一个临时对象,我们可以通过右值引用来修改这个对象。

2. 右值引用作为函数参数

右值引用最有用的地方在于作为函数参数,尤其是在写重载函数时,希望对传入的左值和右值表现出不同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void fun(const int& lref)
{
std::cout << "l-value reference to const." << std::endl;
}
void fun(int &&rref)
{
std::cout << "r-value reference." << std::endl;
}

int main()
{
int x{ 5 };
fun(x); // l-value argument calls l-value version of fun()
fun(5); // r-value argument calls r-value version of fun()
return 0;
}

这段代码打印出:

1
2
l-value reference to const.
r-value reference.

可以看出,当传入的参数是左值时,调用的是左值版本的fun(), 当传入的参数是右值时,调用的是右值版本的fun()。可是这有什么用呢?这对于移动语义来说是一个非常重要的特性,后面会继续讨论。
再看一个有意思的例子:

1
2
int &&ref{ 5 };
fun(ref);

ref是一个右值引用,那么fun(ref)调用的是右值引用版本吗?事实上,这里调用的是左值版本的fun()函数。虽然ref是一个右值引用,但是这仅说明它绑定的对象是一个右值,它本身是一个局部变量,是一个左值,因此这行代码调用的是左值版本的fun()。

3. 不要返回右值引用

在绝大多数情况下,你都不应该返回右值引用。因为右值引用绑定的对象在出作用域之后就会被销毁,因此从函数返回,你智能得到一个”hanging reference”。

参考资料

[1].https://www.learncpp.com/cpp-tutorial/15-2-rvalue-references/