Loading... # 一、完美转发的概念和步骤演绎 ## 1、直接调用 ```cpp #include <iostream> using namespace std; void funcLast1(int v1, int& v2) { ++v2; //改变v2的值,让其自增1 cout << v1 + v2 << endl; } int main() { //case1:直接调用funcLast1,不通过转发 int i = 50; funcLast1(41, i); //92 cout << "i : "<< i <<endl;//51 return 0; } ``` ## 2、转发 转发函数funcMiddle 一般都会写为模板 ```cpp #include <iostream> using namespace std; void funcLast1(int v1, int& v2) { ++v2; //改变v2的值,让其自增1 cout << v1 + v2 << endl; } template<typename F, typename T1,typename T2> void funcMiddle_Temp1(F f, T1 t1, T2 t2) //f:函数指针类型void(*)(int,int),而funcLast是函数类型void(int,int) { f(t1, t2); } int main() { //case2: 通过转发的方式来调用funcLast1 int j = 70; funcMiddle_Temp1(funcLast1, 20, j); //91 cout << "j : "<< j <<endl;//70 //当前情况下j被funcMiddle_Temp推断成了int而不是int& //void funcMiddle_Temp(void(*f)(int,int &),int t1,int t2){...} //**出现问题 j的值无法被修改 相当于左值通过转发之后不再为左值** //solution 参考case3 return 0; } ``` 通过funcMiddle()间接调用funcLast。funcMiddle相当于一个跳板函数。如果有参数,那么参数也需要通过funcMiddle中转传递给funcLast() `出现问题 j的值无法被修改 相当于左值通过转发之后不再为左值,这个时候就要用完美转发来解决!` # 二、完美转发 > const, 左值,右值。实参的属性完全不丢失,原原本本的通过funcMiddle转发给funcLast,这种转发就是`完美转发`。 ```cpp #include <iostream> using namespace std; void funcLast2(int v1, int& v2) { ++v2; //改变v2的值,让其自增1 cout << v1 + v2 << endl; } template<typename F, typename T1,typename T2> void funcMiddle_Temp2(F f, T1 t1, T2&& t2) { f(t1, t2); } int main() { //case3: 利用完美转发T&& 保留左值属性 int j = 70; funcMiddle_Temp2(funcLast2, 20, j); //91 cout << "j : "<< j <<endl;//71 j的值被成功修改 return 0; } ``` **完美转发会遇到的问题,参数中出现右值引用** ```cpp #include <iostream> using namespace std; //参数中出现右值引用 void funcLast3(int&& v1, int& v2) { cout << v1 << endl; cout << v2 << endl; } template<typename F, typename T1,typename T2> //函数模板(跳板函数):把收到的参数以及这些参数相对应的类型不变的转发给其他函数(完美转发) void funcMiddle_Temp3(F f, T1&& t1, T2&& t2) { f(t1, t2); } int main() { //case4 利用完美转发也会遇到新的问题 **出现问题** 就是当last函数的形参为右值的时候 int j = 70; //3_3_1.cpp:64:4: error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’ //下面这句话编译不通过 传入的20是右值 但是通过T&&之后 丢失了右值的属性 解决办法:利用forward转发 //funcMiddle_Temp3(funcLast3, 20, j); //91,执行完本函数,j = 71?70呢? = 70 return 0; } ``` ## 1、要实现完美转发,就要用到std::forward 1. 万能引用:实参的所有信息都会传递到万能引用当中去从而让编译器推导出来函数模板最终的形参类型 (引用折叠)。 引用折叠: T& & —> T& T& && —> T& T&& && —> T&& T&& & —> T& 2.完美转发:就是让程序员可以书写接受任意实参的函数模板(funcMiddle\_Temp),并将其转发到目标函数(funcLast2),目标函数会接收到与 3.转发函数(funcMiddle\_Temp)所接收的完全相同(**当然包括类型相同比如保持参数的左值、右值特性**)的参数。 4.要实现完美转发,就要用到std::forward了。 ```cpp #include <iostream> using namespace std; void funcLast4(int&& v1, int& v2) { cout << __FUNCTION__ <<" "<< v1 << endl; cout << __FUNCTION__<<" "<<v2 << endl; } template<typename F, typename T1,typename T2> //函数模板(跳板函数):把收到的参数以及这些参数相对应的类型不变的转发给其他函数(完美转发) void funcMiddle_Temp4(F f, T1&& t1, T2&& t2) { f(forward<T1>(t1), t2);//f(forward(t1), t2);//不要忘记<T1> } int main() { //case5: 解决完美转发case4的问题 int j = 70; funcMiddle_Temp4(funcLast4, 20, j); //91 cout << "j : "<< j <<endl;//70 return 0; } ``` 万能引用类型才是forward能够发挥作用的重要条件。 理解: * 实参原来是个左值j,到了形参中还是左值t2。forward能够转化回原来该实参的左值或者右值性。所以,forward之后还是个左值。 * 实参原来是个右值20,到了形参中变成了左值t1。forward能够转化回原来该实参的左值或者右值性。所以,forward之后还是个右值。 * forward这个函数有强制把左值转换成右值的能力。所以:forward这个函数只对原来是个右值这种情况有用。 **`forward的能力:保持原始实参的左值性或者右值性`** 一个验证完美转发的Demo ```cpp #include <iostream> using namespace std; void printInfo(int& t) { cout << "printInfo()参数类型为左值引用" << endl; } void printInfo(int&& t) { cout << "printInfo()参数类型为右值引用" << endl; } void printInfo(const int& t) { cout << "printInfo()参数类型为const 左值引用" << endl; } template <typename T> void TestF(T&& t) { printInfo(std::forward<T>(t)); } int main() { TestF(1); //printInfo()参数类型为右值引用 int i = 5; TestF(i); //printInfo()参数类型为左值引用 TestF(std::move(i)); //printInfo()参数类型为右值引用 ----std::move能够将左值转换成右值。 const int j = 8; TestF(j); //printInfo()参数类型为const 左值引用 ------j是个const左值 TestF(int(12)); //printInfo()参数类型为右值引用 ----int(12)是个临时对象,是个右值。 int&& tempvalue = 16;//**虽然是右值引用类型,但本身是个左值** TestF(tempvalue);//printInfo()参数类型为左值引用 return 0; } ``` ## 2、普通参数的完美转发 ```cpp #include <iostream> using namespace std; int getData5() { return 3; } void funcLast5(int v1) { cout << "v1=" << v1 << endl; } void funcMiddle_Temp5() { auto&& result = getData5(); //getData返回的是右值,所以auto = int ,result = int &&(右值引用) //....对result做各种运算。 funcLast5( std::forward<decltype(result)>(result) ); } int getData6() { return 3; } void funcLast6(int v1) { cout << "v1=" << v1 << endl; } void funcMiddle_Temp6() { auto result = getData6(); //getData返回的是右值,所以auto = int ,result = int &&(右值引用) //....对result做各种运算。 funcLast6(result); } int main() { //case7: 普通参数的完美转发 funcLast5(getData5());//本来可以直接调用的 funcMiddle_Temp5(); funcMiddle_Temp6(); return 0; } ``` void funcLast5(int v1) int既可以接左值,也可以接右值 Question: 留一个问题,在讲这个内容的时候,是说普通参数的完美转发,但是这个地方为啥一定要使用完美转发呢?难道就是为了得到一个右值的result? ## 3、在构造函数模板中使用完美转发 ```cpp #include <iostream> #include <cstring> //#include <boost/type_index.hpp> using namespace std; class Human1 { public: ////构造函数 Human1(const string& tmpname) :m_sname(tmpname) { cout << "Human1(const string &tmpname)执行" << endl; } //Human(string&& tmpname) :m_sname(tmpname) Human1(string&& tmpname) :m_sname(std::move(tmpname)) //move并不具备移动能力,把一个左值转换成一个右值。 { cout << "Human1(string&& tmpname)执行" << endl; } string m_sname; }; class Human11 { public: //构造函数模板 其实就等价于上面的两段话 template<typename T> Human11(T&& tmpname) : m_sname(std::forward<T>(tmpname)) { cout << "Human11(T&& tmpname)执行" << endl; } string m_sname; }; int main() { //case1:测试构造函数的完美转发 string sname1 = "ZhangSan"; Human1 myhuman1(sname1);//调用构造函数 Human1 myhuman2(string("LiSi")); //调用移动构造函数 "LiSi"是const char[5]类型,而string("LiSi")是string类型。 string sname11 = "ZhangSan"; Human11 myhuman11(sname11);//调用构造函数 Human11 myhuman22(string("LiSi")); //调用移动构造函数 "LiSi"是const char[5]类型,而string("LiSi")是string类型。 return 0; } ``` 可以使用模板来代替原有的构造函数 ```cpp #include <iostream> #include <cstring> //#include <boost/type_index.hpp> using namespace std; class Human2 { public: //构造函数模板 template<typename T> Human2(T&& tmpname) : m_sname(std::forward<T>(tmpname)) { cout << "Human2(T&& tmpname)执行" << endl; } //拷贝构造函数 Human2(const Human2& th) : m_sname(th.m_sname) { cout << "Human2(const Human2& th)拷贝构造函数执行" << endl; } //移动构造函数 Human2(Human2&& th) : m_sname(std::move(th.m_sname)) { cout << "Human2(Human2&& th)移动构造函数执行" << endl; } //private: string m_sname; }; int main() { //case2:测试当存在拷贝构造和移动构造的时候 string sname1 = "ZhangSan"; Human2 myhuman1(sname1);//调用构造函数 /* 3_3_3.cpp:94:57: error: no matching function for call to ‘std::__cxx11::basic_string<char>::basic_string(Human2&)’ 94 | Human2(T&& tmpname) : m_sname(std::forward<T>(tmpname)) | ^ */ //单独写的拷贝构造函数 还是会去调用template 导致编译报错 //Human2 myhuman3(myhuman1); //实际编译器去调用了构造函数模板,而不是调用了拷贝构造函数。 后面使用std::enable_if //单独写的移动构造函数 就会调用移动构造函数 Human2 myhuman4(std::move(myhuman1));//会调用单独的移动构造函数 //解决办法: } ``` 当完美转发和构造函数、构造移动函数共存时, **单独写的拷贝构造函数 还是会去调用template 导致编译报错,解决办法使用enable\_if** 单独写的移动构造函数 就会调用移动构造函数 ```cpp #include <iostream> #include <cstring> //#include <boost/type_index.hpp> using namespace std; void funcLast1(int v1, int& v2) //目标函数 { ++v2; //改变v2的值,让其自增1 cout << v1 + v2 << endl; } template<typename F, typename T1,typename T2> void funcMiddle_Temp1(F f, T1&& t1, T2&& t2) //转发函数 { f( std::forward<T1>(t1), std::forward<T2>(t2) ); } int funcLast2(int v1, int& v2) //目标函数 { ++v2; //改变v2的值,让其自增1 cout << v1 + v2 << endl; return v1 + v2; } template<typename F, typename T1,typename T2> int funcMiddle_Temp2(F f, T1&& t1, T2&& t2) //转发函数 { return f( std::forward<T1>(t1), std::forward<T2>(t2) ); } int main() { //转发的函数没有返回值 int j = 70; _nmsp2::funcMiddle_Temp1(funcLast1, 20, j); cout << "j = " << j << endl; //转发的函数有返回值 int j2 = 70; int k2 = _nmsp2::funcMiddle_Temp2(funcLast2, 20, j2); cout << "j2 = " << j2 << endl; cout << "k2 = " << k2 << endl; } ``` 利用转发函数类进行转发,当有返回值和没有返回值的时候 ```cpp #include <iostream> #include <cstring> //#include <boost/type_index.hpp> using namespace std; //支持任意数量、类型参数的完美转发 template <typename F, typename...T> void funcMiddle_Temp3(F f, T&&... t)// 当返回为空的时候 { f(std::forward<T>(t)...); } template <typename F, typename...T> //auto funcMiddle_Temp3(F f, T&&... t)->decltype( f(std::forward<T>(t)...) ) decltype(auto) funcMiddle_Temp33(F f, T&&... t)// 当返回不为空的时候 返回值需要自动推断 { return f(std::forward<T>(t)...); } int main() { int j = 70; _nmsp2::funcMiddle_Temp3(funcLast1, 20, j); cout << "j = " << j << endl; int j2 = 70; int k2 = _nmsp2::funcMiddle_Temp33(_nmsp2::funcLast2, 20, j2); cout << "j2 = " << j2 << endl; cout << "k2 = " << k2 << endl; } ``` 支持任意数量、类型参数的完美转发 `decltype(auto) C++14的用法` auto funcMiddle\_Temp33(F f, T&&... t)->decltype( f(std::forward<T>(t)...) ) 等价于 decltype(auto) funcMiddle\_Temp33(F f, T&&... t)// 当返回不为空的时候 返回值需要自动推断 ```cpp #include <iostream> #include <cstring> //#include <boost/type_index.hpp> using namespace std; //目标函数 void funcLast4(char* p) { //if (p != NULL) if (p != nullptr) { strncpy(p, "abc",3); } } //转发函数 template <typename F, typename...T> void funcMiddle_Temp4(F f, T&&... t) { f(std::forward<T>(t)...); } int main() { char* p = new char[100]; memset(p, 0, 100); //funcMiddle_Temp(_nmsp3::funcLast4, NULL); //funcLast4(NULL); funcMiddle_Temp4(_nmsp3::funcLast4, nullptr); return 0; } ``` 最后修改:2025 年 07 月 01 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏