SoFunction
Updated on 2025-03-02

Problem of modifying external variables in Lambda expressions

First look at the problem of modifying external variables in Lambda expressions

Because the platform uses JDK8 and I find that I don’t seem to know anything about new features, such as Lambda expressions, I started to improve my code. . .

For example, when traversing a Map, it is definitely like this when we traversing normally.

String result = "select * from where id = '#userId#' and name = '#userName#'";
Map<String,String> sysParams = new HashMap<String,String>();
("#userId#", "userId");
("#userName#", "userName");
("#realName#", "realName");
("#orgIds#", "orgIds");
("#departname#", "departname");
("#roleId#", "roleId");
 
for(<String, String> entry : ()){
 if(result .contains(())){
 result = result .replaceAll((), (()));
 }
}

But if you use Lambda expressions, it will be very simple.

But we will find that the result will report an error: Local variable result defined in an enclosing scope must be final or effectively final

String result = "select * from where id = '#userId#' and name = '#userName#'";
Map<String,String> sysParams = new HashMap<String,String>();
("#userId#", "userId");
("#userName#", "userName");
("#realName#", "realName");
("#orgIds#", "orgIds");
("#departname#", "departname");
("#roleId#", "roleId");
 
((key,value)->{
 if((key)){
 result = (key, value);
  
 }
});

This is because: Java will pass the value of result as a parameter to the Lambda expression to create a copy of the Lambda expression. Its code accesses this copy, rather than an external declaration of the result variable. Many students may ask why they have to create a copy, how convenient is it to directly access external result variables. The answer is: This is impossible, because the result is defined in the stack. When the Lambda expression is executed, the result may have been released.

Of course, it is OK if you have to modify the value of an external variable in a Lambda expression. You can define the variable as an instance variable or define the variable as an array.

Next I will change it to an array, instance variables are inconvenient.

String result = "select * from where id = '#userId#' and name = '#userName#'";
 // Just define the variable as an array  String[] arr = new String[]{result};
 Map&lt;String,String&gt; sysParams = new HashMap&lt;String,String&gt;();
 ("#userId#", "userId");
 ("#userName#", "userName");
 ("#realName#", "realName");
 ("#orgIds#", "orgIds");
 ("#departname#", "departname");
 ("#roleId#", "roleId");
 
 ((key,value)-&gt;{
 if(arr[0].contains(key)){
      //It's all about the array  arr[0] = arr[0].replaceAll(key, value);
  
 }
 });

Let's take a look at the detailed explanation of C++ lambda capture mode and rvalue reference

Lambda expressions and rvalue references are two very useful features of C++11.

The lambda expression will actually be created by the compiler.std::function Object, variables captured in the form of values ​​will be copied by the compiler and a corresponding const member variable of the same type will be created in the std::function object, such as the following code:

int main(){
 std::string str = "test";
 printf("String address %p in main, str %s\n", &str, str.c_str());
 auto funca = [str]() {
 printf("String address %p (main lambda), str %s\n", &str, str.c_str());
 };
 std::function<void()> funcb = funca;
 std::function<void()> funcc;
 funcc = funca;
 printf("funca\n");
 funca();
 std::function<void()> funcd = std::move(funca);
 printf("funca\n");
 funca();
 printf("funcb\n");
 funcb();
 std::function<void()> funce;
 funce = std::move(funcb);
 printf("funcb\n");
// funcb();
 printf("funcc\n");
 funcc();
 printf("funcd\n");
 funcd();
 printf("funce\n");
 funce();
// std::function<void(int)> funcf = funce;
 return 0;
}

The output of this code is as follows:

Stringaddress0x7ffd9aaab720 in main, strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), str
funcb
Stringaddress0x55bdd2160280 (main lambda), strtest
funcb
funcc
Stringaddress0x55bdd21602b0 (main lambda), strtest
funcd
Stringaddress0x55bdd21602e0 (main lambda), strtest
funce
Stringaddress0x55bdd2160280 (main lambda), strtest

From the output when calling funca above, you can see that the object str captured by the lambda expression in a value form has different addresses inside and outside the lambda expression.

The std::function class object is the same as the ordinary magic board object, and can be copied and constructed, such as:

std::function<void()> funcb = funca;

From the output when calling funcb, you can see that the copy construct is a member-by-member copy construct.

The std::function class object can be assigned values, such as:

std::function<void()> funcc;
funcc = funca;

From the output when calling funcc, you can see that when the assignment is assigned, a member-by-member assignment is performed.

The std::function class object can be constructed in a mobile way, such as:

std::function<void()> funcd = std::move(funca);

After the mobile structure is constructed, the output when funca and funcd is called, you can see that the moving structure is a member-by-member mobile structure.

The std::function class object can move assignments, such as:

 std::function<void()> funce;
 funce = std::move(funcb);

 printf("funcb\n");
// funcb();

Here the call to funcb after moving assignment is commented out. This is because the source funcb is called after moving assignment is called, and an exception will be thrown, such as:

String address 0x562334c34280 (main lambda), str test
funcb
terminate called after throwing aninstanceof 'std::bad_function_call'
 what(): bad_function_call

At the same time, from the output when calling funce, it can be seen that the output is exactly the same as the output when funcb was called before moving the assignment. That is, moving assignment is to move the object as a whole, which is different from the behavior when moving the structure.

The copy construction or assignment of std::function class objects also need to meet the principle of type matching, such as:

std::function<void(int)> funcf = funce;

This line of code will cause compilation failure, and the compilation error message is as follows:

../src/: In function ‘intmain()':
../src/:64:36: error: conversion from ‘std::function<void()>' to non-scalar type ‘std::function<void(int)>' requested
   std::function<void(int)> funcf = funce;
                                    ^~~~~
make: *** [src/] Error 1
src/:18: recipe for target 'src/' failed

The rvalue object captured in a lambda as a value is made only a copy of the captured rvalue object in the lambda std::function object, and the original rvalue has not changed anything.

Next, let’s take a look at a sample code:

#include<iostream>
#include<functional>
#include<string>

using namespace std;

void funcd(std::string&&str){
 printf("String address %p in funcd A, str %s\n", &str, str.c_str());
 string strs = std::move(str);
 printf("String address %p in funcd B, str %s, strs %s\n", &str, str.c_str(), strs.c_str());
}

void funcc(std::stringstr){
 printf("String address %p in funcc, str %s\n", &str, str.c_str());
}

void funcb(std::string&str){
 printf("String address %p in funcb, str %s\n", &str, str.c_str());
}

void funca(std::string&&str){
 printf("String address %p in funca A, str %s\n", &str, str.c_str());
 std::string stra = str;
 printf("String address %p in funca B, str %s, stra %s\n", &str, str.c_str(), stra.c_str());
}

int main(){
 std::string str = "test";
 printf("String address %p in main A, str %s\n", &str, str.c_str());

 funca(std::move(str));
 printf("String address %p in main B, str %s\n", &str, str.c_str());

// funcb(std::move(str));
 printf("String address %p in main C, str %s\n", &str, str.c_str());

 funcc(std::move(str));
 printf("String address %p in main D, str %s\n", &str, str.c_str());

 std::string stra = "testa";
 printf("String address %p in main E, stra %s\n", &stra, stra.c_str());

 funcd(std::move(stra));
 printf("String address %p in main F, stra %s\n", &stra, stra.c_str());

 return 0;
}

When executing the above code, the output is as follows:

String address 0x7ffc833f4660 in main A, str test
String address 0x7ffc833f4660 in funca A, str test
String address 0x7ffc833f4660 in funca B, str test, stra test
String address 0x7ffc833f4660 in main B, str test
String address 0x7ffc833f4660 in main C, str test
String address 0x7ffc833f4680 in funcc, str test
String address 0x7ffc833f4660 in main D, str
String address 0x7ffc833f4680 in main E, stra testa
String address 0x7ffc833f4680 in funcd A, str testa
String address 0x7ffc833f4680 in funcd B, str , strs testa
String address 0x7ffc833f4680 in main F, stra

The funca function receives an rvalue reference as a parameter, and can be seen from the output inside the funca function and before and after the function call. std::move() Nothing is done by itself, and simply calling std::move() will not move the content of the original object to anywhere.std::move() Just a simple cast that converts the lvalue to an rvalue reference. At the same time, it can be seen that using rvalue reference as parameters to construct objects does not have any impact on the objects referenced by rvalue reference.

The funcb function receives an lvalue reference as a parameter. In the above code, the following line is commented out:

//  funcb(std::move(str));

This is because funcb cannot be called with an rvalue reference as an argument. When using an rvalue reference as a parameter and calling a function funcb that receives an lvalue reference as a parameter, the compilation will fail:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/" -MT"src/" -o "src/" "../src/"
../src/: In function ‘int main()':
../src/:34:18: error: cannot bind non-const lvalue reference of type ‘std::__cxx11::string& {aka std::__cxx11::basic_string<char>&}' to an rvalue of type ‘std::remove_reference<std::__cxx11::basic_string<char>&>::type {aka std::__cxx11::basic_string<char>}'
   funcb(std::move(str));
         ~~~~~~~~~^~~~~
../src/:17:6: note:   initializing argument 1 of ‘void funcb(std::__cxx11::string&)'
 void funcb(std::string &str) {
      ^~~~~
src/:18: recipe for target 'src/' failed
make: *** [src/] Error 1

However, if funcb receives a const lvalue reference as a parameter, such as void funcb(const std::string &str), then when calling this function, you can use an rvalue reference as a parameter. At this time, funcb's behavior is basically the same as funca.

The funcc function receives lvalues ​​as parameters. It can be seen from the output inside the funcc function and before and after the function call that with the lvalue as the receiver, the value of the object referenced by the passed rvalue reference is moved and entered the function's parameter stack object.

The funcd function, like the funca function, receives an rvalue reference as a parameter, but the special thing about funcd is that inside the function, the rvalue constructs a new object, so the value of the rvalue referenced object is moved and enters the newly constructed object.

Let’s take a look at another sample code:

#include<iostream>
#include<functional>
#include<string>

using namespace std;

void bar(std::string&&str){
 printf("String address %p in bar A, str %s\n", &str, str.c_str());
 string strs = std::move(str);
 printf("String address %p in bar B, str %s, strs %s\n", &str, str.c_str(), strs.c_str());
}

std::function<void()> bar_bar(std::string &&str) {
 auto funf = [&str]() {
 printf("String address %p (foo lambda) F, stra %s\n", &str, str.c_str());
 };
 return funf;
}

std::function<void()> foo(std::string &&str) {
 printf("String address %p in foo A, str %s\n", &str, str.c_str());

// auto funa = [str]() {
// printf("String address %p (foo lambda) A, str %s\n", &str, str.c_str());
// bar(str);
// };
// funa();
//
// auto funb = [str]() {
// printf("String address %p (foo lambda) B, str %s\n", &str, str.c_str());
// bar(std::move(str));
// };
// funb();

// auto func = [str]() mutable {
// printf("String address %p (foo lambda) C, str %s\n", &str, str.c_str());
// bar(str);
// };
// func();

 auto fund = [str]() mutable {
 printf("String address %p (foo lambda) D, str %s\n", &str, str.c_str());
 bar(std::move(str));
 };
 fund();

 auto fune = [&str]() {
 printf("String address %p (foo lambda) E, str %s\n", &str, str.c_str());
 bar(std::move(str));
 };
 fune();

 std::string stra = "testa";
 return bar_bar(std::move(stra));
}

int main(){
 std::string str = "test";
 printf("String address %p in main A, str %s\n", &str, str.c_str());

 auto funcg = foo(std::move(str));
 printf("String address %p in main B, str %s\n", &str, str.c_str());

 funcg();

 return 0;
}

The output of the above code is as follows:

Stringaddress0x7ffc9fe7c5c0 in main A, strtest
Stringaddress0x7ffc9fe7c5c0 in foo A, strtest
Stringaddress0x7ffc9fe7c540 (foo lambda) D, strtest
Stringaddress0x7ffc9fe7c540 in barA, strtest
Stringaddress0x7ffc9fe7c540 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 (foo lambda) E, strtest
Stringaddress0x7ffc9fe7c5c0 in barA, strtest
Stringaddress0x7ffc9fe7c5c0 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 in main B,str
Stringaddress0x7ffc9fe7c560 (foo lambda) F, stra����

Funa defined in the function foo() and the call to funa are commented out, because this code will cause compilation failure. The specific error message is as follows:

Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/" -MT"src/" -o "src/" "../src/"
../src/: In lambda function:
../src/:25:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘const string {aka const std::__cxx11::basic_string<char>}'
     bar(str);
            ^
../src/:7:6: note:   initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
 void bar(std::string &&str) {
      ^~~
src/:18: recipe for target 'src/' failed
make: *** [src/] Error 1

As we mentioned earlier, when an rvalue reference is captured in a lambda expression, a const object will be generated in the std::function class generated by the compiler for the lambda expression. The const object cannot be used as an rvalue reference to call a function that receives an rvalue reference as a parameter.

The funb defined in the function foo() is wrapped with std::move() on str when calling bar(). However, the compilation will still fail at this time. The error message is as follows:

Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/" -MT"src/" -o "src/" "../src/"
../src/: In lambda function:
../src/:31:18: error: binding reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to ‘std::remove_reference<const std::__cxx11::basic_string<char>&>::type {aka const std::__cxx11::basic_string<char>}' discards qualifiers
     bar(std::move(str));
         ~~~~~~~~~^~~~~
../src/:7:6: note:   initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
 void bar(std::string &&str) {
      ^~~
make: *** [src/] Error 1
src/:18: recipe for target 'src/' failed

In funb, str is a const object, so it still doesn't work.

Func defined in the function foo() has been modified with mutable relative to funa. Compilation will still fail at this time. The error message is as follows:

Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/" -MT"src/" -o "src/" "../src/"
../src/: In lambda function:
../src/:37:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}'
     bar(str);
            ^
../src/:7:6: note:   initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
 void bar(std::string &&str) {
      ^~~
make: *** [src/] Error 1
src/:18: recipe for target 'src/' failed

The lvalue cannot be bound to an rvalue reference.

The fund defined in the function foo() is wrapped in str when calling bar() compared to funcstd::move() . At this time, the compilation can finally be successfully completed, and you can move const str .

The fune defined in the function foo() captures the rvalue reference in a reference manner relative to funb. Calling bar() in fune is just like foo() calling bar() directly.

Call the function bar_bar() that receives an rvalue reference as an argument in the function foo() to generate a function. In the function bar_bar() function object funf defined by lambda, capture an rvalue in the reference form, and access the modified object in the lambda. The lambda is a function object generated by the bar_bar() function. When bar_bar() is called in foo(), the temporary object defined on the function stack is passed into stra, and the function object returned by bar_bar() is returned as the return value. Use funcg to receive the function object returned by the foo() function and call funcg. At this time, crash will occur or you can see garbled code. crash or garbled code is because in funf, the accessed str object is actually a temporary object on the stack defined in the foo() function. After the call to the foo() function is completed, the temporary object on the stack is released. The call to funcg in the main() function is actually accessing an invalid object, so there is a problem.

Summarize

This is the end of this article about modifying external variables in Lambda expressions. For more related contents of C++ lambda expressions, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!