C ++ Functors – 及其用法

我一直听到很多关于函子的C ++。 有人可以给我一个概述,他们是什么,在什么情况下,他们将是有用的?

函子几乎就是定义了operator()的类。 这可以让你创build“看起来像”一个函数的对象:

// this is a functor struct add_x { add_x(int x) : x(x) {} int operator()(int y) const { return x + y; } private: int x; }; // Now you can use it like this: add_x add42(42); // create an instance of the functor class int i = add42(8); // and "call" it assert(i == 50); // and it added 42 to its argument std::vector<int> in; // assume this contains a bunch of values) std::vector<int> out(in.size()); // Pass a functor to std::transform, which calls the functor on every element // in the input sequence, and stores the result to the output sequence std::transform(in.begin(), in.end(), out.begin(), add_x(1)); assert(out[i] == in[i] + 1); // for all i 

关于仿函数有一些好的东西。 一个是不像常规function,它们可以包含状态。 上面的例子创build了一个函数,它将42添加到你给它的任何东西。 但是这个值不是硬编码的,当我们创build我们的函子实例时,它被指定为构造函数参数。 我可以创build另一个加法器,只需通过调用具有不同值的构造函数就可以添加27个加法器。 这使得它们可以很好地定制。

如最后一行所示,您经常将函数作为parameter passing给其他函数,如std :: transform或其他标准库algorithm。 除了正如我上面所说的,函数可以做到“自定义”,因为它们包含状态,使得它们更加灵活(如果我想使用函数指针,我必须写一个函数在它的参数中加上了1,函数是一般的,并添加了你初始化的任何东西),而且它们也可能更有效率。 在上面的例子中,编译器确切地知道std::transform应该调用哪个函数。 它应该调用add_x::operator() 。 这意味着它可以内联该函数调用。 这就像我手动调用向量的每个值的函数一样高效。

如果我传递了一个函数指针,编译器不能立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它将不得不在运行时解引用指针,然后进行调用。

除此之外, 你可以使用boost::function来从函数和方法创build函数,如下所示:

 class Foo { public: void operator () (int i) { printf("Foo %d", i); } }; void Bar(int i) { printf("Bar %d", i); } Foo foo; boost::function<void (int)> f(foo);//wrap functor f(1);//prints "Foo 1" boost::function<void (int)> b(&Bar);//wrap normal function b(1);//prints "Bar 1" 

你可以使用boost :: bind来为这个函子添加状态

 boost::function<void ()> f1 = boost::bind(foo, 2); f1();//no more argument, function argument stored in f1 //and this print "Foo 2" (: //and normal function boost::function<void ()> b1 = boost::bind(&Bar, 2); b1();// print "Bar 2" 

和最有用的,用boost :: bind和boost :: function你可以从类方法创build函数,实际上这是一个委托:

 class SomeClass { std::string state_; public: SomeClass(const char* s) : state_(s) {} void method( std::string param ) { std::cout << state_ << param << std::endl; } }; SomeClass *inst = new SomeClass("Hi, i am "); boost::function< void (std::string) > callback; callback = boost::bind(&SomeClass::method, inst, _1);//create delegate //_1 is a placeholder it holds plase for parameter callback("useless");//prints "Hi, i am useless" 

您可以创build函子的列表或向量

 std::list< boost::function<void (EventArg e)> > events; //add some events .... //call them std::for_each( events.begin(), events.end(), boost::bind( boost::apply<void>(), _1, e)); 

所有这些东西都有一个问题,编译器的错误信息不是人类可读的:)

Functor是一个像function一样的对象。 基本上,定义了operator()

 class MyFunctor { public: int operator()(int x) { return x * 2;} } MyFunctor doubler; int x = doubler(5); 

真正的好处是一个仿函数可以保持状态。

 class Matcher { int target; public: Matcher(int m) : target(m) {} bool operator()(int x) { return x == target;} } Matcher Is5(5); if (Is5(n)) // same as if (n == 5) { ....} 

就像其他人所说的,一个仿函数是一个像函数一样的对象,即它重载函数调用操作符。

函子常用于STLalgorithm。 它们非常有用,因为它们可以在函数调用之前和之间保持状态,就像函数式语言中的闭包。 例如,你可以定义一个MultiplyBy函子,它的参数乘以指定的数量:

 class MultiplyBy { private: int factor; public: MultiplyBy(int x) : factor(x) { } int operator () (int other) const { return factor * other; } }; 

然后你可以将一个MultiplyBy对象传递给像std :: transform这样的algorithm:

 int array[5] = {1, 2, 3, 4, 5}; std::transform(array, array + 5, array, MultiplyBy(3)); // Now, array is {3, 6, 9, 12, 15} 

函子的指针的另一个优点是可以在更多情况下内联该调用。 如果你传递了一个函数指针进行transform ,除非这个调用被内联了,并且编译器知道你总是把相同的函数传递给它,它不能通过指针内联这个调用。

早在C ++出现之前,名称“函数”在传统分类理论中就已经被传统使用了。 这与函子的C ++概念无关。 最好是使用名称函数对象,而不是我们在C ++中所谓的“函子”。 这是其他编程语言如何调用类似的结构。

用来代替普通的function:

特征:

  • 函数对象可能有状态
  • 函数对象适合OOP(它的行为像其他任何对象)。

缺点:

  • 给程序带来更多的复杂性。

用来代替函数指针:

特征:

  • 函数对象通常可以内联

缺点:

  • 函数对象在运行时不能与其他函数对象types交换(至less除非它扩展了一些基类,因此会带来一些开销)

用来代替虚拟function:

特征:

  • 函数对象(非虚拟)不需要vtable和运行时调度,因此在大多数情况下它更有效率

缺点:

  • 函数对象在运行时不能与其他函数对象types交换(至less除非它扩展了一些基类,因此会带来一些开销)

对于像我这样的新手来说:经过一番研究之后,我想出了代码jalf发布的内容。

函子是一个类或结构对象,可以像一个函数一样“调用”。 这可以通过重载() operator() operator (不知道它所调用的)可以接受任意数量的参数。 其他运算符只有两个,即+ operator只能取两个值(运算符两边各一个),并返回已超载的值。 你可以在一个() operator包含任何数量的参数,这是它的灵活性。

要创build一个仿函数,首先创build你的类。 然后你用你select的types和名字的参数为这个类创build一个构造函数。 这是由一个初始化器列表(它使用一个单独的冒号操作符,我也是新来的)在同一个语句中遵循的,它使用之前声明的参数构造类成员对象给构造器。 然后() operator被重载。 最后你声明你创build的类或结构的私有对象。

我的代码(我发现jalf的variables名称混淆)

 class myFunctor { public: /* myFunctor is the constructor. parameterVar is the parameter passed to the constructor. : is the initializer list operator. myObject is the private member object of the myFunctor class. parameterVar is passed to the () operator which takes it and adds it to myObject in the overloaded () operator function. */ myFunctor (int parameterVar) : myObject( parameterVar ) {} /* the "operator" word is a keyword which indicates this function is an overloaded operator function. The () following this just tells the compiler that () is the operator being overloaded. Following that is the parameter for the overloaded operator. This parameter is actually the argument "parameterVar" passed by the constructor we just wrote. The last part of this statement is the overloaded operators body which adds the parameter passed to the member object. */ int operator() (int myArgument) { return myObject + myArgument; } private: int myObject; //Our private member object. }; 

如果有任何不准确或者错误,请随时纠正我!

函数是一个高阶函数 ,它将函数应用于参数化(即模板)types。 这是地图高阶函数的推广。 例如,我们可以像这样为std::vector定义一个函子:

 template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))> std::vector<U> fmap(F f, const std::vector<T>& vec) { std::vector<U> result; std::transform(vec.begin(), vec.end(), std::back_inserter(result), f); return result; } 

这个函数接受一个std::vector<T>并在给定一个函数F并返回一个U时返回std::vector<U> 。 一个仿函数不必定义在容器types上,它也可以被定义为任何模板types,包括std::shared_ptr

 template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))> std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p) { if (p == nullptr) return nullptr; else return std::shared_ptr<U>(new U(f(*p))); } 

下面是一个简单的例子,将types转换为double

 double to_double(int x) { return x; } std::shared_ptr<int> i(new int(3)); std::shared_ptr<double> d = fmap(to_double, i); std::vector<int> is = { 1, 2, 3 }; std::vector<double> ds = fmap(to_double, is); 

有两个法则,仿函数应该遵循。 首先是身份法则,它规定如果函子给定了一个身份函数,它应该和将身份函数应用到这个types一样,即fmap(identity, x)应该与identity(x)相同, :

 struct identity_f { template<class T> T operator()(T x) const { return x; } }; identity_f identity = {}; std::vector<int> is = { 1, 2, 3 }; // These two statements should be equivalent. // is1 should equal is2 std::vector<int> is1 = fmap(identity, is); std::vector<int> is2 = identity(is); 

下一个定律是组合定律,它规定如果函数是由两个函数组成的,它应该和第一个函数使用函数,然后再使用第二个函数一样。 因此, fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))

 double to_double(int x) { return x; } struct foo { double x; }; foo to_foo(double x) { foo r; rx = x; return r; } std::vector<int> is = { 1, 2, 3 }; // These two statements should be equivalent. // is1 should equal is2 std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is); std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is)); 

以下是我被迫使用Functor解决问题的一个实际情况:

我有一套function(比如说20个),除了每个function在3个特定的地方都有不同的function外,它们都是相同的。

这是令人难以置信的浪费和代码重复。 通常我只是传递一个函数指针,然后在3个地方调用它。 (所以代码只需要出现一次,而不是二十次。)

但后来我意识到,在每种情况下,具体的function需要一个完全不同的参数configuration文件! 有时2个参数,有时5个参数等

另一个解决scheme是拥有一个基类,其中特定的函数是派生类中的重写方法。 但是,我真的想要构build所有这些inheritance,只是让我可以传递一个函数指针?

解决方法:所以我做了一个包装类(“Functor”),它可以调用我所需要的任何函数。 我提前设置(使用其参数等),然后传递它,而不是一个函数指针。 现在被调用的代码可以触发Functor,不知道里面发生了什么。 它甚至可以多次调用它(我需要它调用3次)。


就是这样 – 一个实用的例子,Functor变成了一个明显而简单的解决scheme,它使我能够将代码重复从20个函数减less到1个。

除了在callback函数中使用,C ++函数也可以帮助提供matrix类的Matlab喜欢访问风格。 有一个例子 。

在gtkmm中使用函数来将一些GUIbutton连接到一个实际的C ++函数或方法。


如果您使用pthread库来使您的应用程序multithreading,Functors可以帮助您。
要启动线程, pthread_create(..)的参数之一是要在自己的线程上执行的函数指针。
但是有一个不便之处。 这个指针不能是一个指向方法的指针,除非它是一个静态方法 ,或者除非你指定它的类 ,比如class::method 。 另一件事,你的方法的接口只能是:

 void* method(void* something) 

所以你不能在一个线程中运行(以一种简单明了的方式)你的类中的方法,而不需要做额外的事情。

在C ++中处理线程的一个很好的方法就是创build你自己的Thread类。 如果你想从MyClass类运行方法,我所做的是,将这些方法转换为Functor派生类。

另外, Thread类有这样的方法: static void* startThread(void* arg)
这个方法的指针将被用作调用pthread_create(..)的参数。 在arg中startThread(..)应该得到的是一个void*引用,指向任何Functor派生类的堆中的一个实例,执行时它将被转换回Functor* ,然后调用它的run()方法。

为了增加,我已经使用函数对象来将现有的遗留方法适用于命令模式; (只有我觉得OO范式真正的OCP美的地方); 在这里也join相关的函数适配器模式。

假设你的方法有签名:

 int CTask::ThreeParameterTask(int par1, int par2, int par3) 

我们将看到如何将它适用于Command模式 – 为此,首先,您必须编写一个成员函数适配器,以便可以将其作为函数对象调用。

注意 – 这是丑陋的,可能是你可以使用Boost绑定助手等,但如果你不能或不想,这是一种方法。

 // a template class for converting a member function of the type int function(int,int,int) //to be called as a function object template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3> class mem_fun3_t { public: explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3)) :m_Ptr(_Pm) //okay here we store the member function pointer for later use {} //this operator call comes from the bind method _Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const { return ((_P->*m_Ptr)(arg1,arg2,arg3)); } private: _Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature }; 

另外,我们需要一个助手方法mem_fun3来帮助上面的类来调用。

 template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3> mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm) (_arg1,_arg2,_arg3) ) { return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm)); 

}

现在,为了绑定参数,我们必须写一个绑定函数。 所以,这里是:

 template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3> class binder3 { public: //This is the constructor that does the binding part binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k) :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){} //and this is the function object void operator()() const { m_fn(m_ptr,m1,m2,m3);//that calls the operator } private: _Ptr m_ptr; _Func m_fn; _arg1 m1; _arg2 m2; _arg3 m3; }; 

而且,使用binder3类的帮助函数 – bind3:

 //a helper function to call binder3 template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3> binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k) { return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k); } 

现在,我们必须在Command类中使用它。 使用下面的typedef:

 typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3; //and change the signature of the ctor //just to illustrate the usage with a method signature taking more than one parameter explicit Command(T* pObj,F3* p_method,long timeout,const char* key, long priority = PRIO_NORMAL ): m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0), method(0) { method3 = p_method; } 

以下是你如何称呼它:

 F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( &CTask::ThreeParameterTask), task1,2122,23 ); 

注意:f3(); 将调用方法task1-> ThreeParameterTask(21,22,23);.

在下面的链接这个模式的完整的上下文

像已经重复,仿函数是可以被视为函数的类(重载操作符())。

它们对于需要将某些数据与重复或延迟调用关联到某个函数的情况最为有用。

例如,可以使用函数的链表来实现基本的低开销同步协程系统,任务调度器或可中断的文件parsing。 例子:

 /* prints "this is a very simple and poorly used task queue" */ class Functor { public: std::string output; Functor(const std::string& out): output(out){} operator()() const { std::cout << output << " "; } }; int main(int argc, char **argv) { std::list<Functor> taskQueue; taskQueue.push_back(Functor("this")); taskQueue.push_back(Functor("is a")); taskQueue.push_back(Functor("very simple")); taskQueue.push_back(Functor("and poorly used")); taskQueue.push_back(Functor("task queue")); for(std::list<Functor>::iterator it = taskQueue.begin(); it != taskQueue.end(); ++it) { *it(); } return 0; } /* prints the value stored in "i", then asks you if you want to increment it */ int i; bool should_increment; int doSomeWork() { std::cout << "i = " << i << std::endl; std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl; std::cin >> should_increment; return 2; } void doSensitiveWork() { ++i; should_increment = false; } class BaseCoroutine { public: BaseCoroutine(int stat): status(stat), waiting(false){} void operator()(){ status = perform(); } int getStatus() const { return status; } protected: int status; bool waiting; virtual int perform() = 0; bool await_status(BaseCoroutine& other, int stat, int change) { if(!waiting) { waiting = true; } if(other.getStatus() == stat) { status = change; waiting = false; } return !waiting; } } class MyCoroutine1: public BaseCoroutine { public: MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){} protected: BaseCoroutine& partner; virtual int perform() { if(getStatus() == 1) return doSomeWork(); if(getStatus() == 2) { if(await_status(partner, 1)) return 1; else if(i == 100) return 0; else return 2; } } }; class MyCoroutine2: public BaseCoroutine { public: MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {} protected: bool& work_signal; virtual int perform() { if(i == 100) return 0; if(work_signal) { doSensitiveWork(); return 2; } return 1; } }; int main() { std::list<BaseCoroutine* > coroutineList; MyCoroutine2 *incrementer = new MyCoroutine2(should_increment); MyCoroutine1 *printer = new MyCoroutine1(incrementer); while(coroutineList.size()) { for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin(); it != coroutineList.end(); ++it) { *it(); if(*it.getStatus() == 0) { coroutineList.erase(it); } } } delete printer; delete incrementer; return 0; } 

当然,这些例子本身并不有用。 它们只显示函子如何有用,函子本身是非常基本和不灵活的,这使得它们比例如提供什么更有用。

函子也可以用来模拟在一个函数内定义一个局部函数。 参考问题和其他 。

但是一个本地仿函数不能访问外部的自动variables。 lambda(C ++ 11)函数是一个更好的解决scheme。

实现函数作为函数的一大优势是它们可以在调用之间维护和重用状态。 例如,许多dynamic编程algorithm,如用于计算string之间的Levenshtein距离的Wagner-Fischeralgorithm ,通过填充大量结果表来工作。 每次调用这个函数时分配这个表是非常低效的,所以把函数作为一个函数来实现,并且使表成为一个成员variables,这样可以大大提高性能。

下面是一个实现Wagner-Fischeralgorithm作为函子的例子。 注意表是如何在构造函数中分配的,然后在operator()重用,并根据需要resize。

 #include <string> #include <vector> #include <algorithm> template <typename T> T min3(const T& a, const T& b, const T& c) { return std::min(std::min(a, b), c); } class levenshtein_distance { mutable std::vector<std::vector<unsigned int> > matrix_; public: explicit levenshtein_distance(size_t initial_size = 8) : matrix_(initial_size, std::vector<unsigned int>(initial_size)) { } unsigned int operator()(const std::string& s, const std::string& t) const { const size_t m = s.size(); const size_t n = t.size(); // The distance between a string and the empty string is the string's length if (m == 0) { return n; } if (n == 0) { return m; } // Size the matrix as necessary if (matrix_.size() < m + 1) { matrix_.resize(m + 1, matrix_[0]); } if (matrix_[0].size() < n + 1) { for (auto& mat : matrix_) { mat.resize(n + 1); } } // The top row and left column are prefixes that can be reached by // insertions and deletions alone unsigned int i, j; for (i = 1; i <= m; ++i) { matrix_[i][0] = i; } for (j = 1; j <= n; ++j) { matrix_[0][j] = j; } // Fill in the rest of the matrix for (j = 1; j <= n; ++j) { for (i = 1; i <= m; ++i) { unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1; matrix_[i][j] = min3(matrix_[i - 1][j] + 1, // Deletion matrix_[i][j - 1] + 1, // Insertion matrix_[i - 1][j - 1] + substitution_cost); // Substitution } } return matrix_[m][n]; } }; 

我已经“发现”了一个非常有趣的仿函数的使用:当我没有一个好方法的名字时,我使用它们,因为仿函数是一个没有名字的方法;-)

Interesting Posts