forward在委托机制中的应用——完美转发

标签: forward完美转发委托机制
2017-02-07 21:19 63人阅读 评论(0) 收藏 举报
 分类:
C++(25) 

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]

一.前言

在之前,我曾花三篇文章讲述了C++委托机制的封装,到最后可以实现任意类型函数的捆绑,包括lambda表达式的注册。然而,这个委托机制还有一点需要完善,可能看标题大家知道这个需要完善的点儿是什么,不过不知道也关系,这个文章会由浅入深的讲述这个问题的来源,以及解决方案。

二.问题引入

在直接上委托代码之前我们先抽出问题本质,用简单的代码来发现问题,例子来源于<C++ primer>P612: 
这个将编写一个Agent函数,它负责接受一个函数和两个参数,并且用这个函数来调用这两个参数。

template<typename Function,typename Param1,typename Param2>
void Agent(Function f,Param1 p1,Param2 p2){f(p1,p2);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这个函数就相当于委托的一个雏形,一般情况下,这个函数能工作得很好,然而在当f的实例是一个接受引用参数的函数就会出现问题,因为无论左右值在模板的类型推演的过程中是不会带引用的。例如:

template<typename T>
void f(T a){}
f(1) -> T为int
int a;
f(a) -> T为int
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

如何解决这个问题呢?这里有两种方案:

1.使用move()或者ref()来”提醒”模板推演成引用类型。

class A
{
public:A() { cout << "构造" << endl; }A(const A&) { cout << "拷贝" << endl; }A(A&&) { cout << "移动" << endl; }
};
template<typename T>
void X(T a) { }
int main()
{A a;X(ref(a));X(move(a));return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

以上方法虽然在这种情况下能够解决问题,然而在实际过程中还是会有许多局限性:

  • 在参数转发路径上始终要记住保持参数类型。也就是如果转发层数过多,那么参数类型在中途可能还是会有部分修饰丢失。
  • 调用形式始终要和函数参数类型保持一致,不利于维护。

那么,我们就比较希望能够有一种方式能够使得参数在转发的路途中始终保持自身的完整类型。而这个方法就是forward.

三.引用折叠

在讲forward之前需要引入一个新的概念——引用的折叠。 
我们知道在C++语法中规定不能定义引用的引用。然而,可能有部分同学写过如下代码:

typedef int& rint;
int a = 5;
rint b = a;
rint& c = b;
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

而且,这段代码是编译通过的,可能有人就会说,经过换名之后 rint&就是引用的引用。 
然而实际上对于编译器而言rint& 实质就是 int& & <——请注意,两个&之间有空格。 
根据不同的换名可能会有以下几种情况: 
- T& & 
- T&& & 
- T& && 
- T&& &&

然而对于这些情况,编译器会发生引用折叠,如下:

原型 折叠后
T& & T&
T&& & T&
T& && T&
T&& && T&&

所以说利用模板推演和引用折叠可以找到参数的真正类型。

四.forward的使用——prefect forward

首先我们来看forward的源码

template<class _Ty> inline
constexpr _Ty&& forward(typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{   // forward an lvalue as either an lvalue or an rvaluereturn (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
constexpr _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{   // forward an rvalue as an rvaluestatic_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");return (static_cast<_Ty&&>(_Arg));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可见forward是有两个版本的,下面给出上面英文的注解的翻译:

  1. 将一个左值实参按照其真实类型(左值或者右值)转发(转换)。
  2. 将一个右值按照右值类型转发(转换)。

由于篇幅原因我就只剖析第一个重载版本。剖析之前,我还是先要介绍forward的使用。 
虽然forward是模板函数,参数可以自动推演,然而forward的参数是一个remove_reference萃取后的类型,也就是我们可以推演出remove_reference<_Ty>::type,但是无法推演出_Ty。 
所以使用过程中,我们还是要显式给出_Ty;例如: forward< T >(obj)。

所以_Ty就是我们提供的类型T。 
至于remove_reference< T > 作用就是去掉类型T的所有引用修饰。也就是说假如T为Type&或者Type&&,那么remove_reference< T >::type就是Type。这个模板是标准库提供的标准类型转换模板,为了方便阅读,我将其余的一些贴在了文章的最末。 
根据上一段讲述的引用折叠我们知道_Ty&&折叠后就是_Ty的真正类型。 
所以forward就是将左值实参按照给定的类型T进行强转。

了解了forward实现之后,我们来改写上面的Agent函数:

template<typename Function,typename Param1,typename Param2>
void Agent(Function f,Param1&& p1,Param2&& p2){f(forward<Param1>(p1),forward<Param2>(p2));
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这样,当我将左值int传入第一个参数的时候,Param1会根据引用折叠原理自动推演为int&而在调用f的过程中使用forward< Param1 >(p1)将p1保持Param1类型。

根据这个我改写了委托的代码,如下(在这里使用了模板参数包的展开技巧):

template<typename T>
class Delegate
{};
template<typename Return, typename...Params>
class Delegate<Return(Params...)>
{
public:typedef Return (*FunType) (Params...);Delegate(FunType fun) :_fun(fun) {}Return operator () (Params... params) {return _fun(forward<Params>(params)...);   //注意:这里使用了模板参数包的展开技巧}private:FunType _fun;
};
void fun(int&& a) { cout << a << endl; }
int main()
{Delegate<void(int&&)> a(fun);a(2);return 0;
}

forward在委托机制中的应用——完美转发相关推荐

  1. C++11新特性之 std::forward(完美转发)

    上篇博客对右值.右值引用都做了简要介绍. 我们也要时刻清醒,有时候右值会转为左值,左值会转为右值. (也许"转换"二字用的不是很准确) 如果我们要避免这种转换呢? 我们需要一种方法 ...

  2. std:forward 完美转发

    概述:     // TEMPLATE CLASS identity template<class _Ty>     struct identity     {    // map _Ty ...

  3. C++11:forward及完美转发

    简介 一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是原来的类型. 比如: template <typename T> void forwardVa ...

  4. 移动语义(move semantic)和完美转发(perfect forward)

    完整原文链接:https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/ 移动语义(move semantic) ...

  5. 【C++ Primer | 16】std::move和std::forward、完美转发

    右值引用应该是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石: 移动语义:将内存的所有权从一个对象转移到另 ...

  6. C++ std::move/std::forward/完美转发

    右值引用相关的几个函数:std::move, std::forward 和 成员的 emplace_back; 通过这些函数我们可以避免不必要的拷贝,提高程序性能. move 是将 对象的状态 或者 ...

  7. forward完美转发

    forward完美转发 std::forward是一个标准模板函数,它用于实现完美转发,即将输入的参数原封不动地传递给另一个函数,保持其左值或右值的属性. std::forward的作用是根据模板参数 ...

  8. C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward

    实例代码 // 引用折叠,转发.完美转发,forward#include <iostream>using namespace std;template<typename T> ...

  9. 完美转发std::forward、引用折叠与函数模板实际上是一场内存“权力的游戏”

    简介   学习C++的过程中,会遇到各种技术,一种技术的出现往往是在很多其它技术的铺垫下出现的.对于某项技术的学习,如果不连贯往往导致一知半解,似懂非懂.首先,需明确完美转发是干嘛的?其作用是函数模板 ...

最新文章

  1. Google Protocol Buffer 简单介绍
  2. Selector-背景选择器
  3. CF438E-The Child and Binary Tree【生成函数】
  4. React 等框架使用 index 做 key 的问题
  5. golang 导入自定义包_二、Go基本命令及定制自定义第三方包
  6. 升级 python 2.6.6 到 2.7.14 版本(pip工具安装)
  7. maven的setting文件简单配置
  8. ORM 革命 —— 复兴 | ORM Revolution -- Revived
  9. c# json转换实例
  10. Java的System.out.println并不等于C的printf
  11. C# richTextBox滚动到最后一行 显示最后一行 自动跳转最后一行
  12. 在Sharepoint2010配置SMTP服务
  13. Python IDE(集成开发工具)的下载安装教程
  14. ROS2机器人中文教程分享-小鱼动手学和古月居
  15. 建立了一个博客园创业者QQ群
  16. LeetCode每日一题--860. 柠檬水找零(贪心)
  17. Silverlight 2.5D RPG游戏技巧与特效处理:(十四)体感系统
  18. Midjourney之外21款免费的AI Image画图网站集合
  19. css3,background-clip/background-origin的使用场景,通俗讲解
  20. 【PPic】项目中重要第三方组件集成打包测试

热门文章

  1. 计算机考研301数学一攻略,中南大学
  2. 2009 SAP全球技术研发大会圆满落幕
  3. Ubuntu 18.04 使用 Autokey 给 联想台式电脑 关闭fn
  4. 【教程】40G MTP-LC光纤配线架实现4x10G LC布线
  5. 在正式使用计算机账务系统,计算机会计模板答案.doc
  6. 解决maya渲染设置面板切换不了
  7. 64GU盘装机后变成32G,且电脑无法识别问题解决
  8. 给予奉贤区科技小巨人企业扶持额度40万元
  9. 页面动态时间php,HTML实现网页动态时钟
  10. 手机CPU-----智能手机CPU