β

c++11完美转发

鱼儿的博客 100 阅读

我通过下面的例子和注释,充分说明了对C++11完美转发的理解。

//============================================================================
// 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
右值:3
右值:3
右值:1
左值:2

std::forward的实现:

/**
   *  @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类型的剥离:

/// 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强制转换类型为右值引用也很容易理解了:

/**
   *  @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++11完美转发, 感谢原作者分享。

发表评论