zoukankan      html  css  js  c++  java
  • 【C++】函数对象适配器及其底层原理

    函数对象适配器

    bind1st bind2nd

    函数对象适配器用于扩展函数对象的功能,提供额外传入参数的方法。

    例如,我现在有一个一元谓词:

    class Pred {
       public:
        bool operator()(int a) {
            // ...
        }
    };
    

    现在调用某算法,该算法会使用这个一元谓词。

    algorithm(..., ..., Pred());
    

    但与此同时,我们希望这个谓词可以额外实现一个功能,而这个功能依赖一个临时传入的参数。

    于是改了谓词的实现:

    bool operator()(int a, string b){
        // ...
    }
    

    但这个新参数b是无法在调用algorithm(..., ..., Pred());时传进来的。

    此时就要用到适配器了。

    基本步骤:

    让这个谓词的类继承一个类模板binary_function<{参数1类型, 参数2类型, 返回值类型}>,同时还要用const修饰该谓词为一个常函数(实际上是重写了父类的一个方法)。

    注意,一元谓词继承unary_function二元谓词继承binary_function

    class Pred: public binary_function<int, string, bool>{
       public:
        bool operator()(int a, string b) const {
            // ...
        }
    };
    

    然后,在调用算法时,

    通过一个函数bind2nd,将第二个参数绑定给适配器,同时将这个参数传入。

    algorithm(..., ..., bind2nd(Pred(), "param");
    

    另外,我们也可以使用函数bind1st,将第一个参数绑定给适配器,此时额外的参数将传入第一个参数的位置,而算法则会使用第二个参数。

    bind1st 不仅可以扩展一元谓词,也可以绑定二元谓词,将其变为一元谓词

    例如下面的代码意为 x > 5 的谓词:

    bind2nd(greater<int>(), 1);
    

    底层实现

    就函数对象适配器来说,如果不了解其实现原理,往往很难一下子记住使用步骤。

    比如,为什么要继承,为什么模板类型列表是<{参数1类型, 参数2类型, 返回值类型}>

    又比如,bind1st做了什么工作?

    由于这学期一直在搞 web 方向的东西,免不了要写 js,写 js 就免不了接触链式作用域。

    这个东西虽然强大灵活,但也很难真正理解和应用。虽然四处踩坑,但也训练了我相应的能力。

    不说废话了,进入正题。

    当我看到bind2nd(Pred(), "param"),就立刻想到了包装,写 js 总免不了要把一些东西包装起来,扩展功能。

    读了一下库里的实现代码,发现它大概是做了这些事情,与我想的差不多:

    返回一个闭包,将函数对象和额外的参数放在外部作用域,当算法调用时,将额外参数传入。

    用 js 描述如下:

    function bind2nd(fun, extraParam){
        return function(param){
            return fun(param, extraParam);
        }
    }
    

    而在 C++ 里,它是返回了一个类的实例,这个实例通过函数对象及额外参数进行构造,随后在里面进行 () 的重写,包装我们的函数对象。

    实际上到这里就已经很清楚了,不过之后我却一直在思考,为什么还有一个继承的步骤,为什么要继承。

    实际上,js 写到这里也就结束了,但 C++ 是静态类型的语言,在 js 中习以为常的随便传参随便返回,什么 undefined、null 等等,在 C++ 中必须要有明确地定义。

    那么实际上,这个继承就是在处理参数类型及返回值类型。

    再次分析了一下实现代码,发现他做了这些工作:

    我们要继承的类binary_function是一个类模板,继承后进行实例化bind2nd(Pred(), "param")

    此时父类通过using别名指定,将参数及返回值类型记录下来,这些被记录下来的值在bind2nd包装时被使用,用作 () 的重写。

    两个类,一个作类型记录,一个作调用包装,就这样配合着完成适配器的工作。

    大概手写一下:

    这是大概是包装类的实现:

    tmelate<class funType>
    class bind2nd {
       public:
        using p1Type = funType::p1Type;
        using p2Type = funType::p2Type;
        using resultType = funType::resultType;
        binary_function(funType& fun, p2Type extraParam): fun(fun), extraParam(extraParam) {};
        resultType operator()(const p1Type& arg) const {
          return fun(arg, value);
        }
       protected:
        funType fun;
        p2Type extraParam; // the right operand
    };
    

    这个大概是我们要继承的类的实现:

    template<class arg1, class arg2, class result>
    class binary_function {
       public:
        using p1Type = typename arg1;
        using p2Type = typename arg2;
        using resultType = typename result; 
    };
    
  • 相关阅读:
    Jmeter简单教程
    SpringMVC @RequestBody请求参数在postman中的请求
    maven多环境部署
    idea中项目文件颜色含义
    本地Consumer和Producer无法使用远程Kafka服务器的处理办法
    CentOS7 64位下MySQL5.7安装与配置(YUM)
    git学习笔记
    Nginx的启动、重启、关闭命令
    Mysql update 错误
    spring框架源码编译
  • 原文地址:https://www.cnblogs.com/gaolihai/p/13149741.html
Copyright © 2011-2022 走看看