开发者

Lambda Expression vs Functor in C++

I wonder where should we use lambda expression over functor in C++. To me, these two te开发者_高级运维chniques are basically the same, even functor is more elegant and cleaner than lambda. For example, if I want to reuse my predicate, I have to copy the lambda part over and over. So when does lambda really come in to place?


A lambda expression creates an nameless functor, it's syntactic sugar.

So you mainly use it if it makes your code look better. That generally would occur if either (a) you aren't going to reuse the functor, or (b) you are going to reuse it, but from code so totally unrelated to the current code that in order to share it you'd basically end up creating my_favourite_two_line_functors.h, and have disparate files depend on it.

Pretty much the same conditions under which you would type any line(s) of code, and not abstract that code block into a function.

That said, with range-for statements in C++0x, there are some places where you would have used a functor before where it might well make your code look better now to write the code as a loop body, not a functor or a lambda.


1) It's trivial and trying to share it is more work than benefit.

2) Defining a functor simply adds complexity (due to having to make a bunch of member variables and crap).

If neither of those things is true then maybe you should think about defining a functor.

Edit: it seems to be that you need an example of when it would be nice to use a lambda over a functor. Here you go:

typedef std::vector< std::pair<int,std::string> > whatsit_t;

int find_it(std::string value, whatsit_t const& stuff)
{
  auto fit = std::find_if(stuff.begin(), stuff.end(), [value](whatsit_t::value_type const& vt) -> bool { return vt.second == value; });

  if (fit == stuff.end()) throw std::wtf_error();

  return fit->first;
}

Without lambdas you'd have to use something that similarly constructs a functor on the spot or write an externally linkable functor object for something that's annoyingly trivial.

BTW, I think maybe wtf_error is an extension.


Lambdas are basically just syntactic sugar that implement functors (NB: closures are not simple.) In C++0x, you can use the auto keyword to store lambdas locally, and std::function will enable you to store lambdas, or pass them around in a type-safe manner.

Check out the Wikipedia article on C++0x.


Small functions that are not repeated.

The main complain about functors is that they are not in the same place that they were used. So you had to find and read the functor out of context to the place it was being used in (even if it is only being used in one place).

The other problem was that functor required some wiring to get parameters into the functor object. Not complex but all basic boilerplate code. And boiler plate is susceptible to cut and paste problems.

Lambda try and fix both these. But I would use functors if the function is repeated in multiple places or is larger than (can't think up an appropriate term as it will be context sensitive) small.


lambda and functor have context. Functor is a class and therefore can be more complex then a lambda. A function has no context.

#include <iostream>
#include <list>
#include <vector>
using namespace std;

//Functions have no context, mod is always 3
bool myFunc(int n) { return n % 3 == 0; }

//Functors have context, e.g. _v
//Functors can be more complex, e.g. additional addNum(...) method
class FunctorV
{
   public:
   FunctorV(int num ) : _v{num} {}

   void addNum(int num) { _v.push_back(num); }

   bool operator() (int num)
   {
      for(int i : _v) {
         if( num % i == 0)
            return true;
      }
      return false;
   }

   private:
   vector<int> _v;
};

void print(string prefix,list<int>& l)
{
   cout << prefix << "l={ ";
   for(int i : l)
      cout << i << " ";
   cout << "}" << endl;
}

int main()
{
   list<int> l={1,2,3,4,5,6,7,8,9};
   print("initial for each test: ",l);
   cout << endl;

   //function, so no context.
   l.remove_if(myFunc);
   print("function mod 3: ",l);
   cout << endl;

   //nameless lambda, context is x
   l={1,2,3,4,5,6,7,8,9};
   int x = 3;
   l.remove_if([x](int n){ return n % x == 0; });
   print("lambda mod x=3: ",l);
   x = 4;
   l.remove_if([x](int n){ return n % x == 0; });
   print("lambda mod x=4: ",l);
   cout << endl;

   //functor has context and can be more complex
   l={1,2,3,4,5,6,7,8,9};
   FunctorV myFunctor(3);
   myFunctor.addNum(4);
   l.remove_if(myFunctor);
   print("functor mod v={3,4}: ",l);
   return 0;
}

Output:

initial for each test: l={ 1 2 3 4 5 6 7 8 9 }

function mod 3: l={ 1 2 4 5 7 8 }

lambda mod x=3: l={ 1 2 4 5 7 8 }
lambda mod x=4: l={ 1 2 5 7 }

functor mod v={3,4}: l={ 1 2 5 7 }


First, i would like to clear some clutter here.

There are two different things

  1. Lambda function
  2. Lambda expression/functor.

Usually, Lambda expression i.e. [] () {} -> return-type does not always synthesize to closure(i.e. kind of functor). Although this is compiler dependent. But you can force compiler by enforcing + sign before [] as +[] () {} -> return-type. This will create function pointer.

Now, coming to your question. You can use lambda repeatedly as follows:

int main()
{
    auto print = [i=0] () mutable {return i++;};
    cout<<print()<<endl;
    cout<<print()<<endl;
    cout<<print()<<endl;
    // Call as many time as you want
    return 0;
}

You should use Lambda wherever it strikes in your mind considering code expressiveness & easy maintainability like you can use it in custom deleters for smart pointers & with most of the STL algorithms.

If you combine Lambda with other features like constexpr, variadic template parameter pack or generic lambda. You can achieve many things.

You can find more about it here


As you pointed out, it works best when you need a one-off and the coding overhead of writing it out as a function isn't worth it.


Conceptually, the decision of which to use is driven by the same criterion as using a named variable versus a in-place expression or constant...

size_t length = strlen(x) + sizeof(y) + z++ + strlen('\0');
...
allocate(length);
std::cout << length;

...here, creating a length variable encourages the program to consider it's correctness and meaning in isolation of it's later use. The name hopefully conveys enough that it can be understood intuitively and independently of it's initial value. It then allows the value to be used several times without repeating the expression (while handling z being different). While here...

allocate(strlen(x) + sizeof(y) + z++ + strlen('\0'));

...the total code is reduced and the value is localised at the point it's needed. The only thing to "carry forwards" from a reading of this line is the side effects of allocation and increment (z), but there's no extra local variable with scope or later use to consider. The programmer has to mentally juggle less state while continuing their analysis of the code.

The same distinction applies to functions versus inline statements. For the purposes of answering your question, functors versus lambdas can be seen as just a particular case of this function versus inlining decision.


I tend to prefer Functors over Lambdas these days. Although they require more code, Functors yield cleaner algorithms. The below comparison between find_id and find_id2 showcase that result. While both yield sufficiently clean code, find_id2 is slightly easier to read as the MatchName(name) definition is extracted from (and secondary to) the primary algorithm.

I would argue, however, that the Functor code should be placed inside implementation files right above the function definition where it is used to provide direct access to the function definition. Otherwise a Lambda would be better for code-locality/organization.

#include <iostream>
#include <vector>
#include <string>
using namespace std;

struct Person {
    int id;
    string name;
};

typedef vector<Person> People;

int find_id(string const& name, People const& people) {
    auto MatchName = [name](Person const& p) -> bool
    {
        return p.name == name;
    };
    auto found = find_if(people.begin(), people.end(), MatchName);
    if (found == people.end()) return -1;
    return found->id;
}

struct MatchName {
    string const& name;
    MatchName(string const& name) : name(name) {}
    bool operator() (Person const& person)
    {
        return person.name == name;
    }
};

int find_id2(string const& name, People const& people) {
    auto found = find_if(people.begin(), people.end(), MatchName(name));
    if (found == people.end()) return -1;
    return found->id;
}

int main() {
    People people { {0, "Jim"}, {1, "Pam"}, {2, "Dwight"} };
    cout << "Pam's ID is " << find_id("Pam", people) << endl;
    cout << "Dwight's ID is " << find_id2("Dwight", people) << endl;
}
  1. The Functor is self-documenting by default; but Lambda's need to be stored in variables (to be self-documenting) inside more-complex algorithm definitions. Hence, it is preferable to not use Lambda's inline as many people do (for code readability) in order to gain the self-documenting benefit as shown above in the MatchName Lambda.

  2. When a Lambda is stored in a variable at the call-site (or used inline), primary algorithms are slightly more difficult to read. Since Lambdas are secondary in nature to algorithms where they are used, it is preferable to clean up the primary algorithms by using self-documenting subroutines (e.g. Functors). This might not matter as much in this example, but if one wanted to use more complex algorithms it can significantly reduce the burden interpreting code.

  3. Functors can be as simple (as in the example above) or complex as they need to be. Sometimes complexity is desirable and cases for dynamic polymorphism (e.g. for strategy/decorator design patterns; or their template-equivalent policy types). This is a use-case Lambda's can not satisfy.

  4. Functors require explicit declaration of capture variables without polluting primary algorithms. When more-and-more capture variables are required by Lambda's the tendency is to use a blanket-capture like [=]. But this reduces readability greatly as one must mentally jump between the Lambda definition and all surrounding local variables, possibly member variables, and more.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜