完美转发指的是:如果 function() 函数接收到的参数 t 为左值,那么该函数传递给 otherdef() 的参数 t 也是左值;反之如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须为右值。

使用 C++ 98/03 标准下的 C++ 语言,可以采用函数模板重载的方式实现完美转发:

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
31
32
33
34
#include <iostream>
using namespace std;

// 重载被调用函数,查看完美转发的效果
void otherdef(int &t)
{
cout << "lvalue\n";
}

void otherdef(const int &t)
{
cout << "rvalue\n";
}

// 重载函数模板,分别接收左值和右值
template <typename T>
void function(const T &t) // 接收右值参数
{
otherdef(t);
}

template <typename T>
void function(T &t) // 接收左值参数
{
otherdef(t);
}

int main()
{
function(5); // 5 是右值
int x = 1;
function(x); // x 是左值
return 0;
}

对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,由于 t 为 const 类型,所以 otherdef() 函数实际调用的也是参数用 const 修饰的函数,所以输出“rvalue”;

对于左值 x 来说,2 个重载模板函数都适用,C++编译器会选择最适合的参数类型为 T& 的函数模板,进而 therdef() 函数实际调用的是参数类型为非 const 的函数。

此实现方式仅适用于模板函数仅有少量参数的情况,否则就需要编写大量的重载函数模板,造成代码的冗余。


C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)。

以 function() 函数为例:

1
2
3
4
template <typename T>
void function(T&& t) {
otherdef(t);
}

此模板函数的参数 t 既可以接收左值,也可以接收右值。

如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参,如下所示:

1
2
3
4
5
int n = 10;
int & num = n;
function(num); // T 为 int&
int && num2 = 11;
function(num2); // T 为 int &&

其中,由 function(num) 实例化的函数底层就变成了 function(int & & t),同样由 function(num2) 实例化的函数底层则变成了 function(int && && t)。

C++ 11为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

  • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
  • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。

只需要知道,在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。

通过将函数模板的形参类型设置为 T&&,我们可以很好地解决接收左、右值的问题。


但除此之外,还需要解决一个问题,即无论传入的形参是左值还是右值,对于函数模板内部来说,形参既有名称又能寻址,因此它都是左值。那么如何才能将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?

C++11 引入了一个模板函数 forword<T>(),我们只需要调用该函数,就可以很方便地解决此问题

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
#include <iostream>
using namespace std;

// 重载被调用函数,查看完美转发的效果
void otherdef(int &t)
{
cout << "lvalue\n";
}
void otherdef(const int &t)
{
cout << "rvalue\n";
}

// 实现完美转发的函数模板
template <typename T>
void function(T &&t)
{
otherdef(forward<T>(t));
}

int main()
{
function(5);
int x = 1;
function(x);
return 0;
}

可以看到,forword() 函数模板用于修饰被调用函数中需要维持参数左、右值属性的参数。

总的来说,在定义模板函数时,我们采用右值引用的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值;其次,还需要使用 C++11 标准库提供的 forword() 模板函数修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。

转自:http://c.biancheng.net/view/8695.html