目录
一、前言
转发作为C++11的新特性,涉及了右值引用、模板、引用折叠等知识,之前没学好,近来重学了一遍,一是为了自我提升,二是为了准备面试;本文分享一下我对C++转发功能的理解,如有错误望评论留言。
二、疑惑
在提出疑问前,先说回顾C++转发的作用:将一个或多个实参连同类型不变地转发给其他函数(《C++ Primer》(中文第5版,612页))。
疑问:如下代码为何能实现C++转发功能,疑惑点在第4行的 std::forward 函数,该函数不是说返回右值引用(即左值)吗,为啥可以传递给函数 g 的第一个形参(右值引用)?(代码来自《C++ Primer》(中文第5版,614页))
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
void g(int &&i, int &j)
{
cout << i << " " << j << endl;
}
int main(int argc, char *argv[])
{
int i = 9;
flip(g, i, 42);
return 0;
}
三、解惑
3.1、知识点回顾(含 std::move 源码分析)
上文“二、疑惑”中的代码涉及如下知识点:
1)右值引用;
2)模板实参推断(引用折叠);
a)X& &、X& &&、和 X&& & 都折叠成类型 X&;(《C++ Primer》第5版,609页)
b)类型 X&& && 折叠成 C&&;
3)std::move 工作过程(《C++ Primer》第5版,将此部分放在了“转发”的前面);
std::move 函数源码如下(可在 VS 工具中右键函数名转到定义查看源码):
// TEMPLATE FUNCTION move
template<class _Ty> inline
constexpr typename remove_reference<_Ty>::type&&
move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));
}
代码中 std::remove_reference<T>::type 作用是脱去引用剩下类型本身,假设 T 为 X&、X&&,则 std::remove_reference<T>::type 为 X;
注意 std::move 的主要作用是将一个左值转换为右值,因此如果某个函数代码和上述相近,则该函数功能和 std::move 一致。
概括为:调用std::move函数得到一个匿名的右值引用,此值可当作右值。
3.2、解惑(std::forward 源码分析)
在此之前请确保先看了上文的“std::move 工作过程”部分;
下面查看 std::forward 的源码,在 VS 工具中右键函数名转到定义查看(有两个模板函数,即模板函数重载,可接受不同参数);
有没发现,和 std::move 的源码几乎如出一辙;
// TEMPLATE FUNCTION forward
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
constexpr _Ty&& forward(
typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
(PS:为啥突然要分析 std::forward 源码,这是一个偶然发现,之前也反复看了书上的相关章节,无果)
现在回到 “二、疑惑” 中的代码,我在代码中增加了如下注释,希望对你有帮助。
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
f(std::forward<T2>(t2), //根据模板实参推断(引用折叠),T2为int, 则 std::forward<T2>(t2) 返回 int &&,为匿名右值引用,即右值;
std::forward<T1>(t1));//根据模板实参推断(引用折叠),T1为int &,则 std::forward<T1>(t1) 返回 int && &,即 int &,为左值;
//因此上述实参能够传递给函数 g
}
void g(int &&i, int &j)
{
cout << i << " " << j << endl;
}
int main(int argc, char *argv[])
{
int i = 9;
flip(g, i, 42);
return 0;
}
四、参考文章
C/C++学习记录:std::forward 源码分析 / 完美转发的作用
C++11尝鲜:右值引用和转发型引用_zwvista的专栏-CSDN博客
如何获取 C++ 标准库的源码 - klchang - 博客园