完美转发
什么是完美转发:它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
举个例子:
template<typename T>
void function(T t) {
otherdef(t);
}
function() 函数模板并没有实现完美转发。参数 t 为非引用类型,这意味着在调用 function() 函数时,实参将值传递给形参的过程就需要额外进行一次拷贝操作;无论调用 function() 函数模板时传递给参数 t 的是左值还是右值,对于函数内部的参数 t 来说,它有自己的名称,也可以获取它的存储地址,因此它永远都是左值
,也就是说,传递给 otherdef() 函数的参数 t 永远都是左值。无论从那个角度看,function() 函数的定义都不“完美”。
完美转发这样严苛的参数传递机制,C++98标准中几乎不会用到,但 C++11 标准为 C++ 引入了右值引用和移动语义,因此很多场景中是否实现完美转发,直接决定了该参数的传递过程使用的是拷贝语义(调用拷贝构造函数)还是移动语义(调用移动构造函数)
。
C++98如何实现完美转发
事实上,C++98标准下的 C++ 也可以实现完美转发,
使用非const引用
作为函数模板参数时,只能接收左值,无法接收右值;
使用const 引用
既可以接收左值,也可以接收右值.但考虑到其 const 属性,除非被调用函数的参数也是 const 属性,否则将无法直接传递
用 C++ 98/03 标准下的 C++ 语言,我们可以采用函数模板重载的方式实现完美转发
例如:
//接收左值
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;
}
程序执行结果为:
rvalue
lvalue
从输出结果中可以看到,对于右值 5 来说,它实际调用的参数类型为 const T& 的函数模板,对于左值 x 来说,2 个重载模板函数都适用,C++编译器会选择最适合的参数类型为 T& 的函数模板。但是使用次方法是有弊端的,仅适用于模板函数仅有少量参数的情况,否则就需要编写大量的重载函数模板,造成代码的冗余。
右值引用
C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值
在函数模板中的右值引用又被称为万能引用
。
template <typename T>
void function(T&& t) {
otherdef(t);
}
此模板函数的参数 t 既可以接收左值,也可以接收右值。
引用折叠
但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题继续解决,即如果调用 function() 函数时为其传递一个左值引用或者右值引用的实参
,如下所示:
int n = 10;
int & num = n;
function(num); // T 为 int&
int && num2 = 11;
function(num2); // T 为 int &&
C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):
当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。
在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。
std::forward传递属性
通过将函数模板的形参类型设置为 T&&,我们可以很好地解决接收左、右值的问题。但除此之外,还需要解决一个问题,即无论传入的形参是左值还是右值,对于函数模板内部来说,形参既有名称又能寻址,因此它都是左值
。那么他永远不会调用接下来函数的右值版本.
C++11新标准还引入了一个模板函数 forword<T>()
,我们只需要调用该函数,就可以很方便地将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数
//左值版本
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;
}
程序执行结果为:
rvalue
lvalue
此 function() 模板函数才是实现完美转发的最终版本。可以看到,forword() 函数模板用于修饰被调用函数中需要维持参数左、右值属性的参数。
总结
在定义模板函数时:
- 我们采用
右值引用
的语法格式定义参数类型,由此该函数既可以接收外界传入的左值,也可以接收右值; - 还需要使用 C++11 标准库提供的
forword<T>() 模板函数
修饰被调用函数中需要维持左、右值属性的参数。由此即可轻松实现函数模板中参数的完美转发。