【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;
};