I found that the English title is really bad.
This article briefly introduces the "closure. For ease, the title fun with closure is used.
Something like a closure.
I first looked for something a bit like a closure. Show it out. The first thing is C ++ functor:
1 Struct Add_x { 2 Add_x ( Int X): m_x (x ){} 3 Int Operator ()( Int Y ){ Return M_x + Y ;} 4 5 Private : 6 Int M_x; 7 }; 8 9 Int Value = 1 ; 10 11 STD: Transform (input, input + size, result, add_x (value ));
This sectionCodeWe expect to map each element in the input set to the result set using add_x. Here, add_x is a functor. To introduce variable values defined in function stack space to functor, We must copy (or reference) member variables ). As a result, the value defined on the stack is brought to another context.
Let's take a look at a piece of C # code:
1 Ienumerable < Int > Transform ( 2 Ienumerable < Int > Input, 3 Func < Int , Int ,Int > Transformer, 4 Int Factor ){ 5 Foreach ( Int Value In Input ){ 6 Yield Return Transformer (value, factor ); 7 } 8 } 9 10 Int Add ( Int X, Int Y ){ Return X + Y ;} 11 12 Void Main (){ 13 Int [] Array = { 1 , 2 , 3 , 4 , 5 }; 14 Int Factor = 1 ; 15 Transform (array, add, factor). Dump (); 16 }
This Code also applies the add method to a set. To introduce the variable factor defined in the main function into the add method, we pass the factor variable into the transform function as a parameter and then the transformer delegate.
Make a closure
The above two pieces of code are like "closures", but they are not. Next we will create a "real" closure. Use C #, although I would like to use JavaScript.
The first thing is to regard the "function" as the first-class data, or the first-class function. What is the first-class function? Please refer to Wikipedia (http://en.wikipedia.org/wiki/First-class_function). If you do not like English, I will briefly explain: first-class function means that in languages, functions can be passed as parameters to other functions; functions can be returned as return values by other functions. functions can be stored in other data structures as data. Now we can regard the function as the first-class function:
1Func <String,String,Bool> Predicator =Delegate(StringValue,StringPart ){2ReturnValue. Contains (part );3};
Of course, we can also write it as a Lambda expression:
1Func <String,String,Bool> Predicator = (value, part) => value. Contains (part );
Now, if we want to know whether a string contains the jumps string, we can use the following code:
StringData ="A quick brown fox jumps over a lazy dog."; Predicator (data,"Jumps")
However, we do not like the parameter jumps very much. We removed it from the parameter table, so we moved it out as a variable and directly used it in the function data body.
1 StringPartvariable ="Jumps";2Func <String,Bool> Predicator = (value) =>Value. Contains (partvariable );3 StringData ="A quick brown fox jumps over a lazy dog.";4Predicator (data). Dump ();
Now you get the closure! Congratulations.
What is a closure?
So what is a closure? There are two definitions here. Let's take a look at the specific definition before going to bed: in computer science (rather than mathematics), a closure is a reference to a function or function, and the environment information they reference (like a table, which stores every variable referenced in the function that is not declared in the function ).
That is, the closure always has two parts, one is a function, and the other is "taken away" by this function, but it is not a variable table declared in this function (called free variables or outer variables ).
There is another definition that is not so dull: the closure allows you to encapsulate some behaviors (functions are actions) and transmit them like other objects (the function is a first-class function ), however, it still maintains the ability to access the original context (it can also access outer variables ).
It's amazing. How did he implement it?
We use C # as an example, but the implementation methods in other languages are similar. Here, the implementation of C ++ requires the most attention. We will explain it separately. C # The Code is also:
1 StringKey ="U";2 VaRResult = words. Where (WORD => word. Contains (key ));
This is a very simple piece of code. You can compile it, and then use the anti-compiler to reverse it and you will see what the compiler is doing for you. I will show these things in the following figure:
The compiler does two things for us:
(1) The closure has two elements: a function and an external variable referenced by the function. OK. Here the function is word => word. INS ins (key), and the external variable is the key. The compiler encapsulates these two things into a class: closurehelper.
(2) Replace the variable key originally allocated on the function "stack" with closurehelper. Key. At this point, the variable is on the stack. So even if the function runs all over the world, he will always be able to access the original variable closurehelper. Key.
Have you seen it? The lifetime of this variable is actually extended!
Closure's "weird" phenomenon
After learning the implementation details. We can discuss the "weird" phenomenon that may occur when using closure. To say "weird", as long as the Implementation Details of closure are applied, they are actually quite common. The causes of these strange phenomena are basically one: outer-variable is changed in closure.
Example 1:
Suppose we have the following initial code:
1 VaR Words = New List < String > { 2 " The " , " Quick " , " Brown " , " Fox " , " Jump " , 3 " Over " , " A " , " Lazy " , " Dog " 4 }; 5 6 String Key =" U " ; 7 VaR Result = words. Where (WORD => word. Contains (key ));
The output is quick and jump. However, ifProgramTo:
1 StringKey ="U";2Func <String,Bool> Predicate = word =>Word. Contains (key );3Key ="V";4 5 VaRResult = words. Where (predicate );
So what is output? Considering that the key is actually closurehelper. Key, it is easy to know that the key has changed to "v" during predicate execution, so the output is: over. If you still want to understand it, open a linqpad and try it :-).
Example 2:
1 VaR Actionlist = New List <action> (); 2 3 For ( Int I = 0 ; I < 5 ; ++ I ){ 4 Actionlist. Add ( 5 () =>Console. writeline (I )); 6 } 7 8 Foreach (Action action In Actionlist ){ 9 Action (); 10 }
If you have an interview, you may encounter this problem. The output is: 5 5 5 5 5. This is not easy to explain in language. Please refer to the figure below:
Closurehelper is created outside the for loop body, that is, when the outer-variable is capture, there is only one instance globally. Therefore, I actually has a value of 5 after the first loop. In this way, only 5 can be output when the action is actually executed.
To solve this problem, we should not use I as outer variable, but define outer-variable in the loop body:
1 VaR Actionlist = New List <action> (); 2 3 For ( Int I = 0 ; I < 5 ; ++ I ){ 4 Int Outervariable = I; 5 Actionlist. Add ( 6 () => Console. writeline (outervariable )); 7 } 8 9 Foreach (Action action In Actionlist ){ 10 Action (); 11 12 }
In this way, the execution process becomes:
Expected Value: 0 1 2 3 4.
In fact, for Java, the first writing method is not allowed at all. It is a syntax error.
Example 3
Changing outer variable in closure also affects outer variable references in other contexts. For example:
1 IntVariable =2;2 3Action action =Delegate{Variable =3;};4Action ();
After execution, the value of variable is 3.
As you can see, do not change the value of outer varable in closure. In fact, there are additional benefits if you do not change the value of outer variable in closure:
(1) avoid hair loss caused by excessive brain usage;
(2) This type of code is easier to transplant to functional languages, such as F. Because immutable is a basic rule in these languages.
Some functional language paradigms are beyond the scope of this article. I suggest you read the following blog:
(1) http://diditwith.net/default.aspx
(2) http://blogs.msdn.com/ B /dsyme/
C ++ details
As mentioned above, because of the closure, the lifetime of the capture variable is actually extended! This processing method has no problem for languages in C #, Java, F # And other hosting environments. But C ++ (native, sorry, I really hate using C ++ CLI to write programs) has no garbage collector. How does the compiler handle it? Will it prolong the lifetime? The answer is, no. You need to handle this by yourself, otherwise access violation may not occur.
So how can I fix it? The answer is to control capture style. That is, to explain to the compiler how to reference outer variable. Let's first look at how to construct a closure in C ++.
The closure declaration in C ++ can be implemented using lambda expressions. It consists of three parts:
(1) capture method, that is, the capture style we are interested in;
(2) parameter list, which is a parameter table, is the same as a common C/C ++ function;
(3) expression body: the body of a function, which is the same as a common C/C ++ function;
Do not mention the second and third points. The key is the first point. The first point is to clarify a lot of nonsense. It is not as clear as the list. This list comes from http://www.cprogramming.com/c?201751111/c=11-lamb#closures.html:
[] |
Nothing captured |
[&] |
Capture all outer variables by reference |
[=] |
Capture all outer variables by copying (by value) |
[=, & Foo] |
Capture all outer variables by copying, but use reference to capture the foo variable. |
[Bar] |
Capture the bar variable by copying. Do not copy other variables; |
[This] |
Capture the this pointer in the current context by copying; |
This capture method directly affects the declaration form (declared as a value or reference) of the Helper type member variable generated by the compiler, thus affecting the program logic. The Helper type will be generated during capture, and will be copied or referenced Based on the capture type. For example.
1 {2Outer_variable V;//[1]3 4STD: function <Void(Void)> Lambda = [=] () {v. do_something ();};//[2]5Lambda ();//[3]6}
At [1], outer_variable creates an instance, and the default constructor of outer_variable is called. Assume that the instance is v.
In [2], the comparison is complex:
First, a closure instance is created, and V is capture in the form of value is used by the closure instance, so the copy constructor of outer_variable is called. Let's note that the instance of outer_variable is V '.
Second, if STD: function: ctor (const T &) is triggered, the data type T (currently an anonymous closure type) is copied internally. Therefore, V is also copied and constructed as a member variable referenced by value, so the copy constructor of outer_variable is called. Let's note that the instance of outer_variable is v ''.
[2] After the rvalue is finished, the closure instance of rvalue is destructed so that V' is destructed.
[3] The do_something method of v'' is actually called;
Is it annoying? Of course, in the value-based capture mode, it is obvious that the value of outer varavisible cannot be changed.
The outer varable instance is constructed by referencing capture without frequent replication. In addition, you can change the value of outer variable in closure to affect the variables in the initial context. However, pay special attention to the lifetime of the variable.
STD: function <Void(Void)>Func; {outer_variable V;//[1]Func = [&] () {v. do_something ();};//[2]}//[3]Func ();//Undefined behavior.
[1] outer_variable default constructor call, create instance v.
[2] Closure helper instance construction, by referencing capture to v. Since it is called by reference, there is no copy constructor call. Closure helper instance uses the STD: function constructor to initialize STD :: function object. Rvalue closure instance structure.
[3] V analysis structure is out of scope. At this time, the reference of V from the closure helper instance capture of the func object does not exist.
Calling func causes undefined behavior. For details, see C ++ SPEC:
5.1.2 lambda expressions [expr. Prim. Lambda]
22-[NOTE: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding Lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. -End note]
End
All right, it's done. Hope that you have an understanding of closure and know how the compiler handles it. I also know some pitfalls of using closure. If you find anything inappropriate in this article, click here. Welcome to the discussion :-).