Program optimization based on C++ Lambda expressions

  • 2020-05-17 05:57:38
  • OfStack

What is Lambda?

C++ 11 adds a very important feature -- the Lambda expression. All the brothers in camp David are familiar with Objective-C, and many of them have a special liking for block, and use it to implement various callback functions and agents. Some people even choose to use FBKVOController, BlocksKit and other open source frameworks to change the handling of KVO and control events to block. The reason is simple, convenient, and intuitive. Functions are defined and used in the same place. The Lambda expression here is actually very similar to block, but of course if you compare it to the closure in Swift, that's one thing.

This is a short story about C\C++ programmers, and a short story about C++11 -- the new standard just passed...

Don't get me wrong, the "optimization" in the title isn't about improving performance -- Lambda expressions don't do that. Essentially, it's just a kind of grammatical sugar. Without this expression, we can write programs that satisfy our requirements. Just as you would abandon C and use assembly, or abandon assembly and use machine language, the scope of your control is there, no more, no less. But given the choice, I believe most people would choose assembly over machine language, C over assembly, or even C++ over C... . If you do, then I have reason to believe that you will choose the Lambda expression in the C++ new standard, because it really simplifies your program and makes it easier to write. Make your program easier to read and more elegant; It also gives you more to show off to your peers.

Let's start with a practical application

Let's look at an example.

Whether you are a user of C or C++, I am 96.57% sure that you have used C++ standard template library STL (string, vector, etc.) if you are involved in algorithmic development of PC programs. After all, STL's abstraction is good, no white, no white, no white. There is a big category of algorithms in STL. The abstraction of these algorithms is also good. Let's take the sorting algorithm (sort) for example.

Suppose you now have a structure called Student, which contains the terms ID and name -- student Numbers and names, respectively. In an application where the user wants to sort an array of Student from large to small by ID, the program might be written as follows (all the programs in this article were compiled under Visual Studio 2010) :


#include <string>
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
struct Student {
unsigned ID;
string name;
Student(unsigned i, string n) : ID(i), name(n){}
};
struct compareID {
bool operator ()(const Student& val1, const Student& val2)  const {
return val1.ID < val2.ID;
}
};
int main(int argc, char* argv[]) {
Student a[] = {Student(2,  " John " ), Student(0,  " Tom " ), Student(1,  " Lily " )};
sort(a, a+3, compareID());
for(int i=0; i<3; ++i)
cout<<a[i].ID<<'  ' <<a[i].name<<endl;
return 0
}

The program sorts by sort, and then outputs the results by a loop of for. The sorting is done because of the function compardID.

Now suppose that the user's requirements have changed (or another requirement) and you need to sort by the student's name, then you need to write a new affine function as follows:


struct compareName {
bool operator ()(const Student& val1, const Student& val2) const {
return val1.name < val2.name;
}
};

Then change the call to sort to:


sort(a, a+3, compareName());

There's a problem. Are you aware of it? You just want to express a very simple sorting method, but you have to introduce a lot of lines of code to build the corresponding function. If the function is used in a lot of places, the value of establishing it is relatively large. If you only use it in one place, you also have to be able to write so many lines of code in the middle of your flow. On the other hand, the reader of the program, when reading the corresponding part, has to follow his fluent mind, and he is struggling somewhere in the project -- how does compareName or compareID do it?

Yes, yes, as an C++ old bird, you would say, this is unprofessional to write code like this. It is entirely possible to write without creating a functor, such as sorting by ID, by introducing bind in the boost library, as follows:


sort(a, a+3, bind(less<unsigned>(), bind(&Student::ID, _1), bind(&Student::ID, _2)));

If you can write or read this code, I'll concede that your C++ level is reasonable (if you can't read it, that's ok, it's not the point of this article). But is this code really good? Indeed, you can omit the functors. But the problem is that the complexity of the code has greatly increased - even such a simple 1 requirement, bind expression is as complex, more complex 1 point of the requirement to write how complex form ah, this is a kind of torture for bind itself, the people who write the program, the people who read the program - you hold live?

What about the Lambda expression? Well, the sort statement could read:


sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });

And the third function of sort, which looks a little bit odd, is an Lambda expression. If we remove the "[]" at the beginning, the rest of it looks like a function -- you can easily see what this function does: given two Student elements, compare the ID values of the two elements, and return the comparison result -- it's a lot easier to read than the bind result above.
In fact, using the Lambda expression, the above program can be modified to look like this (only main functions are listed) :


int main(int argc, char* argv[]) {
Student a[] = {Student(2,  " John " ), Student(0,  " Tom " ), Student(1,  " Lily " )};
sort(a, a+3, [](const Student& val1, const Student& val2){ return val1.ID < val2.ID; });
for_each(a, a+3, [](const Student& val){cout<<val.ID<<'  ' <<val.name<<endl;});
return 0
}

Where the for_each sentence is used for output -- where the Lambda expression means: for every val, output its ID and Name values -- thus saving the for loop.

Lambda expressions were introduced to make it easier to write and read programs. As with STL1, why not use it?

Basic syntax for Lambda expressions

With the perceptual knowledge, let's analyze the syntax of Lambda expression under 1.

I have no intention of translating the Lambda expressions section of the draft C++ standard (nor do I). I just want to explain the grammar in the most popular way here. Structurally, the Lambda expression can be written as follows:


Lambda-introducer lambda-declarator(opt) compound-statement

Among them, Lambda-introducer is the "[]" which cannot be omitted. Variables may also appear in brackets. Means passing a local variable into an Lambda expression. lambda-declaratoropt is optional and includes a list of parameters to the expression, return value information, mutable declarations (and 1 other information, not discussed here). The final compound-statement is the main content of the expression.

Here's an example:


int n = 10;
[n](int k) mutable -> int { return k + n; };

The second line of the program is an lambda expression, and lambda has almost everything in it (of course, as I said earlier, there is some other information that is not discussed here, so it is not included). Let's analyze the inside 11:

l[n] is Lambda-introducer, and n is a variable, indicating that the variable n in the scope of the expression will be passed into the expression. In the case of this program, the value passed in is 10. Lambda-introducer can specify that a variable is passed in as a value or, in other forms, as a reference. The variants are baidu1, J l(int k) represents the parameter list, which belongs to part 1 of lambda-declarator. You can think of the expression as a functor (as above). Here you specify the parameter list for the functor. If the function's argument list is empty, this part can be omitted. lmutable means whether the variable in the functor can be changed. As an example of compareID, notice that operator () is const. If this mutable is introduced into the lambda expression, the definition of operator() in the corresponding functor will not include this const -- this means that the value of the variable in the functor (Lambda-introducer passed in) can be changed. Discussing the differences between operator() const and operator() is beyond the scope of this article, so check out the C++ tutorial l- > int represents the return type (in this case, int). If the compiler can infer the return type from the code, or if the return type of the Lambda expression is void, the item is omitted. l {return k + n; } is compound-statement: body of the function.

The analysis shows that this Lambda expression is equivalent to a function that reads in an int value, k, and returns this value plus n. According to the above instructions, this expression can be simplified as:


[n](int k){ return k + n; };

The Lambda expression can be stored in std::function < T > Or std: : reference_closure < T > In a variable of type. Where T represents the type of function corresponding to the expression. Take the above expression as an example, its input parameter is int type variable, and its output is int, so in order to save it, it can be written as follows:


function<int(int)> g = [n](int k){ return k + n; };

For another example, the Lambda expression used above:


struct compareName {
bool operator ()(const Student& val1, const Student& val2) const {
return val1.name < val2.name;
}
};
0

Can be stored in function<bool(const Student&, const Student&)> In a variable of this type.

If you don't want to bother writing this, you can also take advantage of another new feature in the C++ standard: type derivation. Using auto as the type of the variable, let the compiler derive the type of the expression itself:


struct compareName {
bool operator ()(const Student& val1, const Student& val2) const {
return val1.name < val2.name;
}
};
1

No problem, g is still a strongly typed variable, but its type is derived by the compiler, and the advantage is that you don't have to write a long variable type J

The Lambda expression is advanced

To conclude, let's look at some advanced USES of C++ Lambda expressions.

Lambda expressions were introduced primarily for functional programming. With the Lambda expression, we can also do some functional programming. For example, the application of 1 function as the return value:


struct compareName {
bool operator ()(const Student& val1, const Student& val2) const {
return val1.name < val2.name;
}
};
2

It is an Lambda expression, enter an integer variable n, and return a function (lambda expression), which receives an int value k and prints out k+n. The usage of g is as follows:


int a[]={1,2,3,4,5,6,7,8,9,0};
function<void (int)> f = g(2);
for_each(a, a+10, f);

It will output: 3, 4, 5, 6, 7, 8, 9, 10, 11, 2

A little bit of functional programming

As for other things, such as the following expression:

[](){}();

Is a valid call. Where "[](){}" represents an Lambda expression whose input parameter is empty and returns void, doing nothing. And then the last () is the call to evaluate it -- it doesn't do anything, but it compiles

Ok, so I'll leave you there. The last thing I want to say about the Lambda expression is that it is defined in the new standard C++11. The old compiler didn't support it (which is why I used VS2010). Want to use it and other benefits of the new standard? Hey, your guy needs an upgrade.


Related articles: