【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; 
};
posted @ 2020-06-13 21:46  高厉害  阅读(294)  评论(0编辑  收藏  举报