完美转发的意义是利用模板实现更大程度的代码复用,不用为了左值右值和const写很多重载。主要能力是原封不动的把函数参数类型(包括左右值和cv限定)转发给另一个函数。
主要依赖于c++11引入的转发引用和forward函数。
#include <iostream>
#include <utility>
void print(int& x) { std::cout << "Lvalue overload: " << x << "\n"; }
void print(int&& x) { std::cout << "Rvalue overload: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
// 完美转发:将 arg 以“原始身份”传入 print
print(std::forward<T>(arg));
}
int main() {
int a = 10;
wrapper(a); // Lvalue
wrapper(20); // Rvalue
}
- 右值引用:右值引用形式上表现为
Type &&,基于这个语法,能实现移动语义和万能转发。从形式上讲,如果Type是一个确定的类型,那么语义上讲也是一个右值引用,而如果Tyep是自动推导的,那语义上则是一个万能引用,能同时容纳左右值,cv。 - 引用折叠规则能调整&,从而使得传入左右值没有区别
forward能保持值原属性进行传输
完美转发和引用一起就会有很有趣的问题:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
void test(string&x){
cout<<"test"<<x<<endl;
}
void test(const string& x){
cout<<"test const"<<x<<endl;
}
void test(string&& x){
cout<<"test rvalue"<<x<<endl;
}
template<typename T>
void test1(T x){
test(x);
}
template<typename T>
void test2(T&& x){
test(x);
}
template<typename T>
void test3(T &&x){
test(static_cast<T &&>(x));
}
int main() {
string a = " abc"; // 左值
const string b = " abc"; // 左值 + const
auto c = std::move(string(" abc")); // 左值(使用右值版本构造的左值)
string && d = std::move(string(" abc")); // 类型为右值引用的左值,没有新对象,临时的生命周期被延长到 `d` 的作用域结束。
auto && e = std::move(string(" abc")); // 类似为右值引用的左值
string _f = " abc"; // move(_f)为右值
test1(a); // test
test1(b); // test
test1(c); // test
test1(d); // test
test1(e); // test
test1(std::move(_f)); // test
cout<<"--------------------------------"<<endl;
_f = " abc";
test2(a); // test
test2(b); // test const
test2(c); // test
test2(d); // test
test2(e); // test
test2(std::move(_f)); // test
cout<<"--------------------------------"<<endl;
_f = " abc";
test3(a); // test
test3(b); // test const
test3(c); // test
test3(d); // test
test3(e); // test
test3(std::move(_f)); // test rvalue
}
这里有很多值得讨论的点:
- auto 推导过程中的左右值,需要注意的是,auto本身不会显式的变为右值引用,只有auto&&才会
- obj && 是右值引用类型,但其本身是一个左值,所以对d,e的使用都需要当作左值来计算
- T a 本质上是值传递,因此对于左值和右值,都会构造(拷贝/移动)一个新的值,const 虽然可以被T推导出来,但是由于本身是按值传递,顶层const会失效(没有意义了,因为复制了新的值,怎么变更都不会影响原值)
- T&& x 是万能引用,但不配合forward/static_cast,并不能达到完美转发