一、右值引用
- 要了解什么是右值引用,首先要了解什么是右值。右值其实是一个相对的概念,与其相对的就是左值。左值与右值最大的区别就是左值是可以被取地址的,右值是不能被取地址的。举例来说:
int a = 1;
中,变量a
就是左值,在内存中是有地址的,而1
就是右值,是不能被取址的。- 对于引用类型来说,左值引用(
&
)和右值引用(&&
)的表达方式不同。那么为什么C++11要强化右值的概念,引入右值引用呢?因为右值引用支持了移动语义,即可以不通过拷贝的方式,将值移动到新对象中。举例来说:来自 C++ Rvalue References Explained [1]
// 左值:
//
int i = 42;
i = 43; // i 是左值
int* p = &i; // i 是左值,因为可以被取址
int& foo();
foo() = 42; // foo() 是左值
int* p1 = &foo(); // foo() 是左值
string &s = "abc"; // error,右值不可以赋给左值
// 右值:
//
int foobar();
int j = 0;
j = foobar(); // foobar() 是右值
int* p2 = &foobar(); // error, 右值不能被取址
j = 42; // 42 是右值
const string &s = "abc"; // 正确,因为常左值也可赋右值
二、万能引用
- 先来看这样一段代码,
fun
函数同时支持了参数为左值和右值的情况。
#include <iostream>
using namespace std;
// 接收左值
void fun(int& lvalue)
{
std::cout << "lvalue = " << lvalue << std::endl;
}
// 接收右值
void fun(int&& rvalue)
{
std::cout << "rvalue = " << rvalue << std::endl;
}
int main()
{
int x = 10;
fun(x); // 左值
fun(10); // 右值
fun(std::move(x)); // std::move(x) == static_cast<int&&>(x) 将左值转发为右值
}
- 这样每次需要重载两个函数实在是麻烦,有没有办法将两个函数写在一个里面呢?万能引用就是实现了功能,所谓万能引用就是同时支持左值参数和右值参数,而且万能引用并不是C++的一个新特性,而是我们自己所实现的功能。万能引用常见形式如下:
template<typename T>
void fun(T&& param)
{
// do something
}
- 有人可能会有疑问,这不是参数接收右值么?怎么能接受左值的?答案是由于模板函数的类型推导导致的。如果传左值,假设为
int&
,传入参数中的类型就为int& &&
,之后经由引用折叠导致最终等价于int&
。
三、引用折叠
first | second | result |
---|---|---|
& | & | 左值 |
& | && | 左值 |
&& | & | 左值 |
&& | && | 右值 |
- 可以看出引用折叠的规则即任意一个引用为左值引用时结果均为左值,只有在两个引用均为右值引用时才是右值引用。
四、完美转发
- 有了引用折叠之后,我们将之前的代码重新改写成:
#include <iostream>
using namespace std;
// 接收左值
void fun(int& lvalue)
{
std::cout << "lvalue = " << lvalue << std::endl;
}
// 接收右值
void fun(int&& rvalue)
{
std::cout << "rvalue = " << rvalue << std::endl;
}
// 万能引用
template<typename T>
void function(T&& param)
{
fun(param);
}
int main()
{
int x = 10;
function(x); // 左值
function(std::move(x)); // 右值
}
- 此时编译后输出结果为:
lvalue = 10
lvalue = 10
- 为什么会出现这样的情况?是因为
param
的确依据传入的参数来确定其左值还是右值,但是在函数内部传给其它函数的时候又会被当作左值传入。这个时候完美转发就来解决这个问题,完美转发可以依据模板T的类型来强制将函数内部的参数转换为对应的类型。重新改写如下:
#include <iostream>
using namespace std;
// 接收左值
void fun(int& lvalue)
{
std::cout << "lvalue = " << lvalue << std::endl;
}
// 接收右值
void fun(int&& rvalue)
{
std::cout << "rvalue = " << rvalue << std::endl;
}
// 万能引用
template<typename T>
void function(T&& param)
{
//fun(param);
fun(std::forward<T>(param)); // 使用std::forward进行完美转发
}
int main()
{
int x = 10;
function(x); // 左值
function(std::move(x)); // 右值
}
- 此时,结果输出为:
lvalue = 10
rvalue = 10
- 大功告成!
参考
[1] C++ Rvalue References Explained
[2] 深入浅出 C++ 11 右值引用
[3] 引用折叠和完美转发