Loading... ## 完美转发 考虑一个简单的问题:在一个函数中要将值向下传递时,需要将原本的左右值关系依次传递下去,考虑下面的代码 ```cpp template<typename T> void testReference(T& lref) { printf("左值引用 %p\n", &lref); } template<typename T> void testReference(T&& rref) { printf("右值引用 %p\n", &rref); } template<typename T> void pass(T&& ref) { testReference(ref); } void test() { pass("114"); pass(std::string("514")); } ``` 无论 `pass` 接收的是一个左值还是一个右值,`ref` 都会被作为左值被传递,因此最终输出会是2个左值,这显然与目的矛盾了。 STL 库中有一个模板函数 `std::forward` 可以用来解决这个问题:将 `pass` 中的 `ref` 修改为 `std::forward<T>(ref)` 即可实现完美转发。 ```cpp template<typename T> void pass(T&& ref) { testReference(std::forward<T>(ref)); } ``` ## 原理 ### 引用折叠 在继续前,需要先了解 C++ 的引用折叠规则: ``` X& & => X& X&& & => X& X& && => X& X&& && => X&& ``` 因此,只有在传递给 `ref` 的是一个右值时,`ref` 的类型才会是 `T&&`。但是 `ref` 本身是一个左值,因此直接传递会是调用左值引用版本。 ### 库函数实现 clang 库中 `std::forward` 的实现如下: ```cpp template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } 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); } ``` 可以看到,关键点在于 `std::remove_reference<_Tp>`,clang 库中它的实现是 ```cpp 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; }; ``` 可以看出,它的作用是提供一个抹除类型 `_Tp` 的引用的类型 `type`。 因此,`typename std::remove_reference<_Tp>::type&` 和 `typename std::remove_reference<_Tp>::type&&` 就分别代表了原始类型 `T` 的左值引用和右值引用,从而实现了当`_Tp` 为左值引用类型和右值引用类型时调用了不同的 `forward`,并将 `__t` 转换为一个右值引用。不过,从实现上来看,它们做的实际上是同一件事:返回 `static_cast<_Tp&&>(__t)`。 根据引用折叠规则,当 `__t` 是左值时,虽然将它 cast 成了右值引用,最终还是返回的一个左值;当 `__t` 是右值时,就会返回一个右值。 因此,现在我们来总结一下,`std::forward<T>(ref)` 基本等价于 `static_cast<T&&>(ref)`,其原理都是引用折叠: ``` ref = T& => T&&(ref) = T& ref = T&& => T&&(ref) = T&& ``` ## 优点与局限 既然 `std::forward<T>(ref)` 等价于 `static_cast<T&&>(ref)`,那为什么还需要它呢?考虑下面几种情况: + 应该将左值作为左值转发 + 应该将右值作为右值转发 + 应该将派生类型的表达式转发到可访问的,明确的基本类型 + 不应转发任意类型的转换 其中(1)(2)是 `std::forward` 和 `static_cast` 都能做到的,而 (3)(4) 是 `static_cast` 不能做到的(虽然在现代编辑器下会有警告)。考虑下面的代码 ```cpp struct Wrapper { Wrapper(int); }; template<class Arg1, class Arg2> Arg1&& f(Arg1&& a1, Arg2&& a2) { return static_cast<Arg1&&>(a2); // typing error: a1=>a2 } template<class Arg1, class Arg2> Arg1&& g(Arg1&& a1, Arg2&& a2) { return std::forward<Arg1>(a2); // typing error: a1=>a2 } ``` 其中第一个函数不会出现编译错误,而第二个会。因此使用 `std::forward` 更加安全。 不过,毕竟 `std::forward` 包了一层,在不考虑编译器优化的情况下多了一层调用,这也算是唯一的缺点吧。 最后修改:2021 年 11 月 28 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏