我经常听到很多关于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.
}; 

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

其他回答

函子也可以用来模拟在函数中定义局部函数。参考这个问题和另一个问题。

但是局部函子不能访问外部的自动变量。lambda (c++ 11)函数是一个更好的解决方案。

如上所述,函子是可以被视为函数的类(重载操作符())。

在需要将某些数据与对函数的重复或延迟调用相关联的情况下,它们非常有用。

例如,函子链表可用于实现基本的低开销同步协程系统、任务分派器或可中断文件解析。 例子:

/* 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;
}

当然,这些例子本身并没有多大用处。它们只是展示了函子是如何有用的,函子本身是非常基础和不灵活的,这使得它们不如boost所提供的有用。

对于像我这样的新手来说:经过一些研究,我弄清楚了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喜欢的访问样式。这里有一个例子。

函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为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>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括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)));
}

下面是一个将类型转换为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);

函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即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;
    r.x = 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));