In this article, on the example of implementing a callback mechanism, the possibilities of using lambda functions in a convenient and fast form will be considered.
Formulation of the problem
It is necessary to implement a convenient and fast mechanism for storing a “pointer” to an arbitrary function and then calling it with the argument passed (for example, take the char * type).
Method 1 - on the classic "C"
Solving the problem "in the forehead" you can get something like this:
The mechanism is very simple and often used. But with a large number of callbacks, their announcement is not very convenient.
')
Lambda functions in C ++
For those who have not heard about C ++ 11 (or C ++ 0x) or have not yet touched it, I will tell you about some of the innovations from this standard. In C ++ 11, the auto keyword appeared, which can be set instead of type when declaring a variable with initialization. In this case, the type of the variable will be identical to the type indicated after “=”. For example:
auto a=1;
But the most interesting is the lambda function. In principle, these are normal functions, but which can be declared directly in the expression:
[](int a,int b) -> bool
The syntax of the lambda function is as follows:
[ ]()-> { }
The “-> return type” chunk may be missing. Then "-> void" is meant. Another usage example:
int main(int argc,char *argv[]){
This program will output:
5 10 0.563585 0.001251 Press enter to continue...
In this example, three variables (f1, f2 and f3) of type auto were declared and initialized, therefore the type of which corresponds to the type on the right - the type of lambda functions.
The lambda function, by itself, is not a pointer to a function (although in some cases it can be applied to it). The compiler calls the function not by the address but by its type - that is why each lambda of the function has its own type, for example, "<lambda_a48784a181f11f18d942adab3de2ffca>". This type cannot be specified, so it can only be used in conjunction with auto or templates (where the type can also be automatically defined there).
The standard also allows conversion from lambda type to type of pointer to function, in the absence of captured variables:
void(*func)(int arg); func= [](int arg){ ... };
Captured variables are those variables that “get inside” the lambda function when specified:
int main(int argc,char *argv[]){ auto f=[argc,&argv](char *s){ puts(s); for(int c=0;c<argc;c++){ puts(argv[c]); } }; f("123"); return 0; }
These parameters are actually saved (copied by value) in the variable f.
If you specify the & sign in front of the name, the parameter will be passed by reference, and not by value.
The address of the function itself is still not stored anywhere.
Method 2 - Implementation in C ++
Replacing a static function with a lambda can simplify our example:
int main(){ void (*MyCallback)(char *argument);
This is how a little adding “pluses” can greatly simplify life, the main thing is not to overdo it, which we are now trying to do. In this example, such a construction will work until we want to “capture” the variables in the lambda functions. Then the compiler will not be able to convert lambda to a pointer. Here, using C ++, you can do this:
class Callback{ private:
Like this. A little bit of pluses and a code several times more. A cumbersome implementation, and yet the possibility of copying Callback instances has not been taken into account here. But the convenience of use at altitude. Similarly, the modest operation “=” hides the allocation of dynamic memory, and even the constructor does not clearly fit into the concept of code visibility widely loved by those who are faithful to the classic “C” programmers.
Let's try to fix it and speed up and simplify the implementation as much as possible without losing convenience.
Method 3 - Something Mean
Implementation:
class Callback{ private: void (*function)(char*,void*); void *parameters[4]; public: Callback(){ function=[](char*,void*){ }; } template<class T> void operator=(T func){
First of all: we removed a large piece associated with virtual functions and memory allocation. Saving occurs at copy speeds of a few bytes.
The call is also quick - from calling two nested functions (auxiliary and stored) to one, when the compiler embeds one into the other - almost an ideal variant (one extra argument “parameters” separates from the ideal).
For such an implementation, the only limitation is the maximum size of the variables captured in the lambda functions. But usually it is required to transfer not so many additional parameters. And with a large number, you can use the dynamic memory at the expense of speed.
Total
The convenience and functionality of the transfer function as a pointer has been brought to a high level of convenience without a special increase in resource intensity. As for the functional, there is still plenty of room for creativity: creating a queue with priorities (event flow), a template for different types of argument, etc.