我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?


当前回答

对于像我这样的新手来说:经过一些研究,我弄清楚了jalf发布的代码是做什么的。

函子是一个类或结构对象,可以像函数一样被“调用”。这可以通过重载()操作符实现。()操作符(不确定被调用的对象)可以接受任意数量的参数。其他操作符只能取两个值,即+操作符只能取两个值(在操作符的两边各一个),并返回你重载它的任何值。你可以在()操作符中放入任意数量的参数,这就是它的灵活性。

要创建函子,首先要创建类。然后使用您选择的类型和名称参数创建类的构造函数。在同一语句中,后面跟着一个初始化列表(它使用一个冒号操作符,这也是我第一次接触),它使用前面声明的构造函数形参构造类成员对象。然后()操作符被重载。最后,声明已创建的类或结构的私有对象。

我的代码(我发现jalf的变量名令人困惑)

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

如果这是不准确的或完全错误的,请随时纠正我!

其他回答

除了在回调中使用,c++函子还可以帮助为矩阵类提供Matlab喜欢的访问样式。这里有一个例子。

在gtkmm中使用函子将一些GUI按钮连接到实际的c++函数或方法。


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

void* method(void* something)

因此,如果不做一些额外的事情,你就不能(以一种简单明显的方式)在线程中运行你的类中的方法。

在c++中处理线程的一个很好的方法是创建你自己的Thread类。如果你想运行MyClass类的方法,我所做的是,把那些方法转换成Functor派生类。

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

就像其他人提到的,函子是一个像函数一样工作的对象,即它重载函数调用操作符。

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

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

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

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,除非该调用被内联,并且编译器知道你总是将同一个函数传递给它,否则它不能通过指针内联调用。

除此之外,我还使用了函数对象使现有的遗留方法适合命令模式;(我唯一感受到面向对象范式真正OCP之美的地方);在这里还添加了相关的函数适配器模式。

假设你的方法有签名:

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 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);

此模式的完整上下文在下面的链接中

函子基本上就是一个定义操作符()的类。这让你可以创建“看起来像”函数的对象:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  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

函子有几个优点。其一,与常规函数不同,它们可以包含状态。上面的例子创建了一个函数,无论你给它什么,它都会加上42。但是值42并不是硬编码的,它是在创建函数实例时作为构造函数参数指定的。我可以创建另一个加法器,只需要用不同的值调用构造函数,就可以加27。这使得它们可以很好地定制。

As the last lines show, you often pass functors as arguments to other functions such as std::transform or the other standard library algorithms. You could do the same with a regular function pointer except, as I said above, functors can be "customized" because they contain state, making them more flexible (If I wanted to use a function pointer, I'd have to write a function which added exactly 1 to its argument. The functor is general, and adds whatever you initialized it with), and they are also potentially more efficient. In the above example, the compiler knows exactly which function std::transform should call. It should call add_x::operator(). That means it can inline that function call. And that makes it just as efficient as if I had manually called the function on each value of the vector.

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