What are C++ functors and their uses?

2018-12-31 00:15发布

I keep hearing a lot about functors in C++. Can someone give me an overview as to what they are and in what cases they would be useful?

15条回答
看淡一切
2楼-- · 2018-12-31 00:29

Like has been repeated, functors are classes that can be treated as functions (overload operator ()).

They are most useful for situations in which you need to associate some data with repeated or delayed calls to a function.

For example, a linked-list of functors could be used to implement a basic low-overhead synchronous coroutine system, a task dispatcher, or interruptable file parsing. Examples:

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

Of course, these examples aren't that useful in themselves. They only show how functors can be useful, the functors themselves are very basic and inflexible and this makes them less useful than, for example, what boost provides.

查看更多
唯独是你
3楼-- · 2018-12-31 00:33

For the newbies like me among us: after a little research I figured out what the code jalf posted did.

A functor is a class or struct object which can be "called" like a function. This is made possible by overloading the () operator. The () operator (not sure what its called) can take any number of arguments. Other operators only take two i.e. the + operator can only take two values (one on each side of the operator) and return whatever value you have overloaded it for. You can fit any number of arguments inside a () operator which is what gives it its flexibility.

To create a functor first you create your class. Then you create a constructor to the class with a parameter of your choice of type and name. This is followed in the same statement by an initializer list (which uses a single colon operator, something I was also new to) which constructs the class member objects with the previously declared parameter to the constructor. Then the () operator is overloaded. Finally you declare the private objects of the class or struct you have created.

My code (I found jalf's variable names confusing)

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

If any of this is inaccurate or just plain wrong feel free to correct me!

查看更多
ら面具成の殇う
4楼-- · 2018-12-31 00:33

Here's an actual situation where I was forced to use a Functor to solve my problem:

I have a set of functions (say, 20 of them), and they are all identical, except each calls a different specific function in 3 specific spots.

This is incredible waste, and code duplication. Normally I would just pass in a function pointer, and just call that in the 3 spots. (So the code only needs to appear once, instead of twenty times.)

But then I realized, in each case, the specific function required a completely different parameter profile! Sometimes 2 parameters, sometimes 5 parameters, etc.

Another solution would be to have a base class, where the specific function is an overridden method in a derived class. But do I really want to build all of this INHERITANCE, just so I can pass a function pointer????

SOLUTION: So what I did was, I made a wrapper class (a "Functor") which is able to call any of the functions I needed called. I set it up in advance (with its parameters, etc) and then I pass it in instead of a function pointer. Now the called code can trigger the Functor, without knowing what is happening on the inside. It can even call it multiple times (I needed it to call 3 times.)


That's it -- a practical example where a Functor turned out to be the obvious and easy solution, which allowed me to reduce code duplication from 20 functions to 1.

查看更多
旧时光的记忆
5楼-- · 2018-12-31 00:39

Name "functor" has been traditionaly used in category theory long before C++ appeared on the scene. This has nothing to do with C++ concept of functor. It's better to use name function object instead of what we call "functor" in C++. This is how other programming languages call similar constructs.

Used instead of plain function:

Features:

  • Function object may have state
  • Function object fits into OOP (it behaves like every other object).

Cons:

  • Brings more complexity to the program.

Used instead of function pointer:

Features:

  • Function object often may be inlined

Cons:

  • Function object can not be swapped with other function object type during runtime (at least unless it extends some base class, which therefore gives some overhead)

Used instead of virtual function:

Features:

  • Function object (non-virtual) doesn't require vtable and runtime dispatching, thus it is more efficient in most cases

Cons:

  • Function object can not be swapped with other function object type during runtime (at least unless it extends some base class, which therefore gives some overhead)
查看更多
旧人旧事旧时光
6楼-- · 2018-12-31 00:39

Except for used in callback, C++ functors can also help to provide a Matlab liking access style to a matrix class. There is a example.

查看更多
有味是清欢
7楼-- · 2018-12-31 00:39

Functor can also be used to simulate defining a local function within a function. Refer to the question and another.

But a local functor can not access outside auto variables. The lambda (C++11) function is a better solution.

查看更多
登录 后发表回答