What are C++ functors and their uses?

后端 未结 14 1294
花落未央
花落未央 2020-11-21 04:27

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?

相关标签:
14条回答
  • 2020-11-21 04:39

    A functor is a higher-order function that applies a function to the parametrized(ie templated) types. It is a generalization of the map higher-order function. For example, we could define a functor for std::vector like this:

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

    This function takes a std::vector<T> and returns std::vector<U> when given a function F that takes a T and returns a U. A functor doesn't have to be defined over container types, it can be defined for any templated type as well, including 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)));
    }
    

    Heres a simple example that converts the type to a 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);
    

    There are two laws that functors should follow. The first is the identity law, which states that if the functor is given an identity function, it should be the same as applying the identity function to the type, that is fmap(identity, x) should be the same as 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);
    

    The next law is the composition law, which states that if the functor is given a composition of two functions, it should be the same as applying the functor for the first function and then again for the second function. So, fmap(std::bind(f, std::bind(g, _1)), x) should be the same as 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));
    
    0 讨论(0)
  • 2020-11-21 04:40

    A big advantage of implementing functions as functors is that they can maintain and reuse state between calls. For example, many dynamic programming algorithms, like the Wagner-Fischer algorithm for calculating the Levenshtein distance between strings, work by filling in a large table of results. It's very inefficient to allocate this table every time the function is called, so implementing the function as a functor and making the table a member variable can greatly improve performance.

    Below is an example of implementing the Wagner-Fischer algorithm as a functor. Notice how the table is allocated in the constructor, and then reused in operator(), with resizing as necessary.

    #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];
        }
    };
    
    0 讨论(0)
  • 2020-11-21 04:46

    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.

    0 讨论(0)
  • 2020-11-21 04:49

    Like others have mentioned, a functor is an object that acts like a function, i.e. it overloads the function call operator.

    Functors are commonly used in STL algorithms. They are useful because they can hold state before and between function calls, like a closure in functional languages. For example, you could define a MultiplyBy functor that multiplies its argument by a specified amount:

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

    Then you could pass a MultiplyBy object to an algorithm like 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}
    

    Another advantage of a functor over a pointer to a function is that the call can be inlined in more cases. If you passed a function pointer to transform, unless that call got inlined and the compiler knows that you always pass the same function to it, it can't inline the call through the pointer.

    0 讨论(0)
  • 2020-11-21 04:50

    Little addition. You can use boost::function, to create functors from functions and methods, like this:

    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"
    

    and you can use boost::bind to add state to this functor

    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"
    

    and most useful, with boost::bind and boost::function you can create functor from class method, actually this is a delegate:

    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"
    

    You can create list or vector of functors

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

    There is one problem with all this stuff, compiler error messages is not human readable :)

    0 讨论(0)
  • 2020-11-21 04:51

    A functor is pretty much just a class which defines the operator(). That lets you create objects which "look like" a function:

    // 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
    

    There are a couple of nice things about functors. One is that unlike regular functions, they can contain state. The above example creates a function which adds 42 to whatever you give it. But that value 42 is not hardcoded, it was specified as a constructor argument when we created our functor instance. I could create another adder, which added 27, just by calling the constructor with a different value. This makes them nicely customizable.

    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.

    If I had passed a function pointer instead, the compiler couldn't immediately see which function it points to, so unless it performs some fairly complex global optimizations, it'd have to dereference the pointer at runtime, and then make the call.

    0 讨论(0)
提交回复
热议问题