c++11完美转发
我通过下面的例子和注释,充分说明了对C++11完美转发的理解。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
//============================================================================ // Name : haha.cpp // Author : owenliang1990 // Version : // Copyright : Your copyright notice // Description : Hello World in C++, Ansi-style //============================================================================ #include <iostream> void printVar(int&& var) { std::cout << "右值:" << var << std::endl; } void printVar(int &var) { std::cout << "左值:" << var << std::endl; } template <typename T> void func(T&& var) { printVar(std::forward<T>(var)); // 上述代码也可以这样写: // printVar(static_cast<T&&>(var)); // 当你看透了一切, 就知道既然T&& var与func的实参类型一致,那么static_cast<T&&>(var)其实就是再强调一次var的类型给编译器看而已。 // 什么折叠不折叠的,真的不是很重要 } int main(int argc, char** argv) { // 基本概念: // 1,左值、右值:可以修改的叫左值,不能修改的叫右值 // 2,左右值引用 、左右值:左右值引用都是左值,都可以修改 // 实参右值, 必须被右值引用接纳 printVar(1); // 实参左值,必须被左值/左值引用接纳 int a = 2; printVar(a); // *** 实参右值引用,默认视作左值接纳 int&& b = 3; printVar(b); // *** 实参右值引用,(显式)暗示编译器按右值引用接纳 printVar(static_cast<int&&>(b)); // 模板参数T&&可以接纳任意类型的实参: func(static_cast<int&&>(b)); // T的类型推导为int&&,变量T&& var的类型折叠为int&& func(1); // T的类型推导为int,变量T&& var的类型为int && func(a); // T的类型推导为int&,变量T&& var的类型为int& // func中的forward可以对var进行类型转换,保证调用printVar时进入正确的重载函数(回顾上面***代码),它的行为如下: // func(static_cast<int&&>(b)):static_cast<int&& &&>(var) == static_cast<int&&>(var),(暗示编译器)进入printA(int&&) // func(1):static_cast<int &&>(int&&) == static_cast<int&&>(var),(暗示编译器)进入printA(int&&) // func(a):static_cast<int& &&>(int&) == static_cast<int&>(var),进入printA(int&) // 无论哪种情况,都如同直接调用printA函数的期望一致,此行为称为"完美转发"。 // 最后:只有模板类型T可以折叠,非模板不存在折叠规则(如下代码希望折叠,但是会报语法错误) // int &d = static_cast<int& &&>(a); return 0; } |
输出如下:
1 2 3 4 5 6 7 |
右值:1 左值:2 左值:3 右值:3 右值:3 右值:1 左值:2 |
std::forward的实现:
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 |
/** * @brief Forward an lvalue. * @return The parameter cast to the specified type. * * This function is used to implement "perfect forwarding". */ template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } /** * @brief Forward an rvalue. * @return The parameter cast to the specified type. * * This function is used to implement "perfect forwarding". */ template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); } |
在我的例子中,forward函数调用显式传递了模板类型:forward<T>(a),通过remove_reference剥离出type为int,然后会进入第一个forward重载函数,这是因为无论func的var是左值引用还是右值引用,传递给forward时都是左值。
remove_reference利用模板类的偏特化,实现对int类型的剥离:
1 2 3 4 5 6 7 8 9 10 11 12 |
/// remove_reference template<typename _Tp> struct remove_reference { typedef _Tp type; }; template<typename _Tp> struct remove_reference<_Tp&> { typedef _Tp type; }; template<typename _Tp> struct remove_reference<_Tp&&> { typedef _Tp type; }; |
forward函数接下来利用func函数推导的T类型进行&&折叠,并对变量进行类型转换:
如果T是int【func(1)的场景】,那么int &&就是int&&,右值类型得到保持。
如果T是int&&【func(static_cast<int&&>(b))的场景】,那么int&& &&就是int&&,右值引用类型得到保持。
如果T是int&【func(a)的场景】,那么int& &&就是int&,左值类型得到保持。
经过static_cast转换后的__t类型保持了func参数var的原本面貌,对printA的调用提供了强烈的类型暗示。
其他
std::move强制转换类型为右值引用也很容易理解了:
1 2 3 4 5 6 7 8 9 |
/** * @brief Convert a value to an rvalue. * @param __t A thing of arbitrary type. * @return The parameter cast to an rvalue-reference to allow moving it. */ template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } |
同样利用了万能模板参数接受传参,利用remove_referrence提取基础类型type,并将参数__t强制转换为type&&。
这里除了move自身的万能模板参数利用了折叠原则,在static_cast部分并没有采用折叠,而是直接明确转换为了右值引用type&&,这一点与forward的实现是完全不同的,一定要注意仔细区分。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

文中出现的概念都讲到解释到了。👍
虽然从来没真正掌握过c++,然而一直爱着❤️c++❤️。
666
1