STL Functors
目录

1. functor 仿函数

所谓仿函数,是定义了operator()的对象。 因此可以通过调用仿函数对象的operator()函数模拟函数调用。

仿函数的定义形式是:

class FunctionObjectType {
public:
void operator() (...){
...
}
}

仿函数有三个特点:

  • 仿函数比函数更灵巧,因为它可以拥有状态(因为它是对象,带有成员变量)。进而可以有两个不同状态的实体(instance)。
  • 每个仿函数都有类型,因此可以当作template参数传递。
  • 执行速度上,仿函数通常比函数指针更快(?)。

传递仿函数和传递普通对象一样,都是通过passed by value,拷贝一个新的仿函数对象给目标函数作为行参。 因此行参的改变不会影响实参。 如果想得到仿函数的结果,可以使用:

  • 以by reference方式传递仿函数。
  • 运用for_each()返回仿函数。

预定义仿函数

STL提供了预定义的仿函数,在<functional>头文件里。

算术:

functors 效果
negate() - param
plus() param1 + param2
minus() param 1 - param2
multiplies() param1 * param2
divides() param1 / param2
modulus () param1 % param2

关系:

functors 效果
equal_to() param1 == param2
not_equal_to() param1 ! = param2
less() param1 < param2
greater() param1 > param2
less_equal() param1 <= param2
greater_equal() param1 >= param2

逻辑:

functors 效果
logical_not() ! param
logical_and() param1 && param2
logical_or () param1

2. Predicates 判断式

所谓判断式,就是返回布尔值的函数或仿函数。

在STL中,因为仿函数对象是拷贝传递的,因此此时不应该传递一个“仿函数行为取决于被拷贝次数或被调用次数”的仿函数。 就是说,应当把仿函数的operator()声明为const成员函数。 也就是确保判断式是“纯函数”

3. Function Adapters 函数配接器

函数配接器是指能够将仿函数和另一个仿函数/值/一般函数结合起来的仿函数。

bind2nd(greater<int>(), 42),通过配接器bind2nd将一个二元仿函数greater转换为一维仿函数,把42作为greater的第二个参数。

预定义的函数配接器有:

function adapters 效果
bind1st(op, value) op(value, param)
bind2nd(op, value) op(value, param)
not1(op) !op(param)
not2(op) !op(param1, param2)

我们可以对配接器组合(not1(bind2nd(module<int>(), 2))),这样我们可以形成强大的表达式, 这种编程方式称为function composition(功能复合、函数复合)。

如果自定义的仿函数也想用配接器,必须符合以下条件:

  • 如果仿函数的operator()只有一个实参,那么它应该从std::unary_function继承。
  • 如果仿函数的operator()有两个实参,那么它应该从std::binary_function继承。

针对成员函数的函数配接器

function adapters 效果
mem_fun_ref(op) 调用op,那是某对象的一个const成员函数
mem_fun(op) 调用op,那是某对象指针的一个const成员函数

这两个函数传入的是成员函数,如使用mem_fun_ref(&Person::print)的形式调用Person对象的print成员。 比如:

vector<Person> coll; ...
for_each(coll.begin(), coll.end(), mem_fun_ref(&Person::print));

vector<Person*> coll; ...
for_each(coll.begin(), coll.end(), mem_fun(&Person::print));

依次对coll中对象调用print成员函数。注意调用的成员函数必须是const

对象成员函数如果想传递给算法,必须使用men_fun_ref或mem_fun包装对象成员函数。

针对一般函数的函数配接器

function adapters 效果
ptr_fun(op) op(param), op(param1, param2)

上一节针对的是类的成员函数,这一节的函数配接器针对一般的函数。 如:

pos = find_if(coll.begin(), coll.end(), bind2nd(ptr_fun(strcmp), ""));

对于一般的算法(如find_if,for_each),直接传递函数指针给算法是可以的,不必使用ptr_fun包装一般函数指针。 但是对于四个预定义的配接器(not1, not2, bind1st, bind2st),他们必须接受仿函数类型的对象,此时必须使用ptr_fun包装一般函数指针。

4 effective STL: 遵循按值传递的原则设计仿函数

C和C++都不允许将函数传递给另一个函数,相反,只能传递函数指针,如标准库中的qsort:

void qsort(void *base, size_t nmemb, size_t size, int(*cmpfun)(const void*, const void*));

STL的函数对象/仿函数是函数指针的一种抽象和建模形式。 在STL中,函数对象是按值(拷贝)传递的,如for_each函数:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator first, InputIterator last, UnaryFunction f);

上面for_each接受一个函数对象并返回一个函数对象,这两个都是拷贝传递的。 为了函数对象正常工作,必须:

  • 函数对象尽可能小,减小拷贝代价
  • 函数对象要是单态的,不能使用virtual。因为拷贝时如果接受的是基类,传入的是派生类,将产生切割(slicing)动作。

因此对于一个复杂的函数对象,可以使用impl方法,建一个impl类。 这样只需impl类保证上面两条就可以了。

发表评论