Skip to content

C++语言导学(7): 函数对象(Function Object)

函数对象(Function Object)

定义了operator()操作的对象就称为Function Object:

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

FunctionObjectType fo;
fo();

相比较一般的函数,函数对象是带有类型,进而可以有各自的状态,而且通常比函数指针更快。

这是一个通过函数对象对自定义类型在容器中如何排序的例子:

class Person{
 public:
  string firstname() const;
  string lastname() const;
  //...
};

class PersonSortCriterion{
 public:
   bool operator(cosnt Persion& p1, const Persion& p2) const{
       return p1.lastname() < p2.lastname() || 
       (p1.lastname() == p2.lastname() &&
        p1.firstname() < p2.firstname());
   }
};

auto func = []sortPerson(const Person& p1, const Person& p2{
     return p1.lastname() < p2.lastname() || 
           (p1.lastname() == p2.lastname() &&
           p1.firstname() < p2.firstname());
}

set<Person, PersonSortCriterion> coll; // function obj

set<Person, decltype(func)> coll; // lambda形式

for(auto pos = coll.begin(); pos != coll.end(); ++pos){
    //...
}

这里因为函数对象带有类型,可以作为set的模板参数传入; STL中有预定义的一系列函数对象,比如Greater升序排序。

函数适配器(Function Adapter): bind

函数适配器是指能够将不同的函数对象结合起来的东西,最重要的适配器就是 bind()

bind()一般用来将参数绑定到可调用对象,还可以通过预定义占位符_1,_2,...等代指实参,通过bind()定义的函数对象可以称为binder

比如:

#include <functional>
#include <iostream>
using namespace std;
using namespace std::placeholders;

int main() {
    auto plus10 = bind(plus<int>(), _1,10);
    cout << "+10 by 7: " << plus10(7) << "\n";

    auto plus10time2 = bind(multiplies<int>(),plus10,2);
    cout << "+10 * 2 by 7: " << plus10time2(7) << "\n";

    auto pow3 = bind(multiplies<int>(),bind(multiplies<int>(),_1,_1),_1);
    cout << "x*x*x by 7: " << pow3(7) << "\n";

    auto inverDivede = bind(divides<double>(),_2,_1);
    cout << "invdiv by 7/49: " << inverDivede(49,7) << "\n";
}

/*
output:
      +10 by 7: 17
      +10 * 2 by 7: 34
      x*x*x by 7: 343
      invdiv by 7/49: 0.142857
*/
}

binder常用在算法函数中作为函数对象参数:

std::transform(coll.begin(), coll.end(), coll.begin(),bind(plus<int>(),_1,10)); //必须指明binder类型
auto pos = std::find_if(coll.begin(), coll.end(),bind(greater<int>(),_1,42));

binder参数传递方式

有传值和传引用两种方式:

void incr(int& i)
{
    ++i;
}

int incr2(const int&i)
{
    return i+1;
}

int i = 0;

bind(incr, i)();      // 改变的是i的拷贝,不影响外面的i

bind(incr, ref(i))(); // 会改变变量i的值, reference

auto j = bind(incr2, cref(i))(); // 不会改变变量i的值, const reference

注意这里的incr无法重载,因为绑定的只是一个函数名字。

举例: binder调用全局函数

char myToupper(char c){
    std::locale loc;
    return std::use_facet<std::ctypes<char>>(loc).toupper(c);
}

string s("Internationlization"): //i18n
string sub("Nation");

auto pos = search(s.begin(),s.end(), // binder形式
                  sub.begin(),sub.end(),
                  bind(equal_to<char>(),
                       bind(myToupper,_1),
                       bind(myToupper,_2)));

auto pos2 = search(s.begin(), s.end(), // lambda形式
                  sub.begin(), sub.end(),
                  [](char c1, char c2)
                  {return mToupper(c1) == myToupper(c2);});

if(pos != s.end()){
    cout << "\"" << sub << "\" is part of \"" << s << "\"\n";
}                     

举例: binder调用成员函数

class Person{
 private:
    string name;

 public:
    Person(cosnt string& n): name(n){}

    void print()const {
        cout << name << endl;
    }

    void print2(const string& prefix) const{
        cout << prefix << name << endl;
    }
};

vector<Person> coll = {Person("Tick"),Person("Trick"),Person("Track")};

// binder
for_each(coll.begin(),coll.end(),bind(&Person::print, _1));
for_each(coll.begin(),coll.end(),bind(&Person::print2, -1, "Person:"));

bind(&Person::print2,_1,"This is: ")(Person("nico"));

for_each(coll.begin(),coll.end(),std::mem_fn(&Person::print)); // std::mem_fn省去占位符
for_each(coll.begin(),coll.end(),bind(std::mem_fn(&Person::print2),_1,"Person:"));

std::mem_fn(&Person::print)(Person("nico"));
std::mem_fn(&Person::print2)(Person("nico"), "This is: ");

// lambda
for_each(coll.begin(),coll.end(),[](const Person& p){ p.print();});
for_each(coll.begin(),coll.end(),[](const Person& p){ p.print(2,"Person: ");});

举例: binder绑定至数据成员

// 累加value值
map<string, int> coll{{"a",1},{"b",3},{"c",5}};

int sum = accumulate(coll.begin(), coll.end(), 0, 
                     bind(plus<int>(),_1,bind(&map<string,int>::value_type::second, _2))); // binder

int sum = accumulate(coll.begin(), coll.end(), 0, 
                     [](const int&a, const auto &x) -> int { return a + x.second; }); // lambda

Binder对比Lambda

简单的函数绑定时,lambda会更加直观:

auto plus10 = bind(plus<int>(), _1,10);
auto plus10 = [](int i){ return i+10;};

auto plus10time2 = bind(multiplies<int>(),plus10,2);
auto plus10time2 = [](int i){ return (i+10)*2;};

auto pow3 = bind(multiplies<int>(),bind(multiplies<int>(),_1,_1),_1);
auto pow3 = [](int i){ return i*i*i; };

auto inverDivede = bind(divides<double>(),_2,_1);
auto inverDivede = [](double d1, double d2){ return d2/d1;};

但是带有状态的情况下时,binder更不易出差:

class MeanValue{
    private:
        long num;
        long sum;
    public:
        MeanValue():num(0),sum(0){}
        void operator()(int elem){
            ++num;
            sum += elem;
        }
    double value(){
        return static_cast<double>(sum) / static_cast<double>(num);
    }
};

vector<int> coll = {1,2,3,4,5,6,7,8};

MeanValue mv = for_each(coll.begin(), coll.end(), MeanValue());
cout << "Mean Value: " << mv.vlaue() << endl;

long sum = 0;
for_each(coll.begin(), coll.end(), [&sum](int elem){ sum += elem; });
double mv = static_cast<double>(sum) / static_cast<double>(coll.size());
cout << "Mean Value: " << mv << endl;

总结

一般情况下,首选lambda,选择lambda会更加直观,需要维护状态时可选择binder。