标准库函数 std::move

既然编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

int a;
int &&r1 = a; // 编译失败
int &&r2 = std::move(a); // 编译通过

完美转发 std::forward

完美转发适用于这样的场景:需要将一组参数原封不动的传递给另一个函数

“原封不动”不仅仅是参数的值不变,在 C++ 中,除了参数值之外,还有一下两组属性:左值/右值和 const/non-const。完美转发就是在参数传递过程中,所有这些属性和参数值都不能改变,同时,而不产生额外的开销,就好像转发者不存在一样。在泛型函数中,这样的需求非常普遍。

下面举例说明:

#include <iostream>
using namespace std;

template <typename T> void process_value(T & val)
{
cout << "T &" << endl;
}

template <typename T> void process_value(const T & val)
{
cout << "const T &" << endl;
}

//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(const T& val)
{
process_value(val);
}

template <typename T> void forward_value(T& val)
{
process_value(val);
}

int main()
{
int a = 0;
const int &b = 1;

//函数 forward_value 为每一个参数必须重载两种类型,T& 和 const T&
forward_value(a); // T&
forward_value(b); // const T &
forward_value(2); // const T&

return 0;
}

对于一个参数就要重载两次,也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说,是非常低效的。

那C++11是如何解决完美转发的问题的呢?实际上,C++11是通过引入一条所谓“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来完成完美转发。

typedef const int T;
typedef T & TR;
TR &v = 1; //在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式

C++11中的引用折叠规则:

TR的类型定义

声明v的类型

v的实际类型

T &

TR

T &

T &

TR &

T &

T &

TR &&

T &

T &&

TR

T &&

T &&

TR &

T &

T &&

TR &&

T &&

一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。

C++11中,std::forward可以保存参数的左值或右值特性:

#include <iostream>
using namespace std;

template <typename T> void process_value(T & val)
{
cout << "T &" << endl;
}

template <typename T> void process_value(T && val)
{
cout << "T &&" << endl;
}

template <typename T> void process_value(const T & val)
{
cout << "const T &" << endl;
}

template <typename T> void process_value(const T && val)
{
cout << "const T &&" << endl;
}

//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(T && val) //参数为右值引用
{
process_value( std::forward<T>(val) );//C++11中,std::forward可以保存参数的左值或右值特性
}

int main()
{
int a = 0;
const int &b = 1;

forward_value(a); // T &
forward_value(b); // const T &
forward_value(2); // T &&
forward_value( std::move(b) ); // const T &&

return 0;
}

参考资料:
1、​​​C++11 标准新特性: 右值引用与转移语义​​​
2、​​​深入理解C++11:C++11新特性解析与应用​