左值/右值引用与完美转发

C++11之前,并没有严格的左值和右值概念,没有这些概念依然能编写出可靠的程序。而在C++11中引入了右值,以及右值引用这样的概念,最主要的作用有两个:

  • 1、引入移动语义,增加了移动构造函数以及移动赋值操作符;
  • 2、实现完美转发。

第一个作用是大多用户常用的,对右值引用带来影响感触最深的变化,例如实现了大量标准库算法性能的优化,将一些以前编译器能做的和不能做的优化都通过简明的逻辑实现了优化。
而本文主要探讨的是右值引用的第二个作用,完美转发的实现对C++标准有着深刻的影响,因为一般用在模板泛型编程上,所以大多用户感受不深,但要知道大量的标准库函数都应用了“转发”技术实现的模板,才能为我们提供相当大的方便。

开始之初,先来厘清左值和右值的概念,这对理解完美转发十分重要。

  • 左值:可以作为表达式左侧的值,有名字,有地址;
  • 右值:包括将亡值和纯右值(与‘左值’相对的字面上的‘右值’),其中将亡值是一切不具备范围内可控生命周期的值比如临时变量、返回值等。

那么右值和左值之间可以相互转化么?
利用std::move()函数就看以将一个左值强制转换为右值;但我们无法将一个右值转化为左值,很显然这是行不通的。而且一个右值也无法自然地接受一个左值,即左值不能隐式地转换为右值,只能通过static_cast ::Type&&>的类型转换,而标准库将之包装成了std::move()函数,因为将一个左值转换为右值通常发生在需要移动该左值的时候进行移动语义的匹配。
虽然左值无法接受右值,但是一个常量左值引用却可以引用一个常量/非常量右值(常量右值并没有实际用处)。从C++98开始,常量左值引用就是一个万能引用,它可以绑定一切类型的值。这一点被传承到C++11上一个最大的好处就是,如果一个类没有移动构造函数,则下列语句:
A a = std::move(a);
将调用以常量左值引用为参数的拷贝构造函数。这是一种智能兼容化的设计,移动构造函数不能用,还可以使用拷贝构造函数,对老代码中不能形成移动构造函数的类有着良好的兼容性。

理解了上面的内容,就可以理解完美转发了。
所谓完美转发,是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。比如:

void f1(int& i);
void f2(int&& i);
template <typename T>
void Function(T t) { f1(t);f2(t);}

当我们调用以下代码:

int i = 1;
Function(i);

生成的模板函数是void Function(int t),从而i在传递给f1之前就被拷贝了一次,这样的转发虽然是正确的,但谈不上完美,因为f1函数的功能无法通过这个函数模板实现了。
为此C++11引入了新的模板推导规则,以及一个引用折叠的规则,我们先给出实现完美转发的函数模板代码:

void f1(int& i);
void f2(int&& i);
template <typename T>
void Function(T&& t) { f1(static_cast<T&&>(t));f2(static_cast<T&&>(t));}

对这样一个模板,当我们调用代码:

int i = 1;
Function(i);

生成的模板函数是void Function(int& && t),经过引用折叠成为void Function(int& t)。
而如果调用Function(1),则生成的模板函数为void Function(int&& && t),经过引用折叠成为void Function(int&& t)。
也就是说这个模板能正确地将传入的参数进行引用了,那么为什么函数体内仍需要static_cast (t)呢?其实这里对f1做不做这一步都无所谓,参数能正确地通过左值引用进行转发。关键是f2这里会出差错。因为此时传给f2的参数t, 虽然是一个接受了右值的右值引用,但它本身却是个左值!仔细体会这句话,联系上面对左值和右值的定义,你应该能恍然大悟了吧。
在C++11中将static_cast ()包装成了std::forward()函数,在C++11的标准库中我们可以看到大量完美转发的实际应用。这里给个典型的例子:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

这个用来生成unique_ptr的函数模板,可以避免代码中“new”关键字的出现,使得代码整体风格更标准,但它接受的参数却与对应类的构造函数参数一致,完美转发功不可没!
不过对于完美转发而言,右值引用并非“天生神力”,只是C++11新引入了右值,因此为其新定下了引用折叠的规则,以满足完美转发的需求。

你可能感兴趣的:(引用)