关于C++转发你有这个疑问吗

目录

一、前言

二、疑惑

三、解惑

3.1、知识点回顾(含 std::move 源码分析)

3.2、解惑(std::forward 源码分析)

四、参考文章


一、前言

转发作为C++11的新特性,涉及了右值引用、模板、引用折叠等知识,之前没学好,近来重学了一遍,一是为了自我提升,二是为了准备面试;本文分享一下我对C++转发功能的理解,如有错误望评论留言。

关于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 - 博客园

右值引用 - Microm - 博客园

上一篇:ValueError: cannot convert float NaN to integer


下一篇:学习C++要注意的那点事