• lambda表达式


    1. lambda表达式的形式

    lambda表达式的形式(可以省略参数列表和返回类型,但必须包含捕获列表和函数体):
    [capture list](parameter list) -> return type {function body}
    更详细点:
    [捕获参数列表](函数参数列表) mutable throw(类型)->返回值类型 {函数语句};

    空捕获列表表明lambda不使用它所在函数中的任何局部变量。

    lambda不能有默认参数,因此一个lambda调用的实参数目永远与形参数目相等。

    lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。

    #ifndef TEST_LAMBDA_H_
    #define TEST_LAMBDA_H_
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		auto f = [](){int a = 9; return 8;};
    
    		std::cout << f() << std::endl;                  // 8
    		std::cout << typeid(f()).name() << std::endl;   // int
    		
    		std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
    		std::sort(strs.begin(), strs.end(), [](const std::string& a, const std::string& b){return a.size() < b.size();});
    
    		for(int i = 0; i < strs.size(); ++i){
    			std::cout << strs[i] << std::endl;
    		}
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出:

    Testing lambda......
    8
    int
    1
    123
    123456
    12345678
    1234567890
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2. lambda表达式的本质

    #include 
    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
            /* 1. lambda expression只是一个表达式expression。
    		   2. 一个闭包closure是一个被lambda创建的运行时对象object。
    		   3. 一个闭包类closure class源自创建闭包实例的类,每个lambda都会让编译器产生一个独特的闭包类(即闭包类创建了lambda对象)。
    		   
    		   闭包是可以被拷贝的,因此一个闭包类型closure type可能生成很多个闭包closures。
    		*/
    		{
    			int i = 9;
    			auto c0 = [i]()mutable{
    				std::cout << "i=" << ++i;
    				std::cout << ", lambda expression defined in: " << __func__ << ", " << __LINE__ << std::endl; // 在__func__的位置会输出"operator ()",证实了c0内心住着一个functor
    			};
    			
    			/* 可以拷贝闭包,但拷贝完成之后就"各自为政"了,谁也不影响谁,
    			   比如进行值捕获的i,在c0、c1、c2、c3中是各自有一个副本,彼此互不影响;但对每个闭包调用多次会修改闭包内变量i的值,
    			   总的来说,对闭包进行拷贝时,如果是值捕获则被捕获的变量在各个拷贝的闭包独自运行互不影响;如果是引用捕获则各个拷贝共同修改被捕获变量 */
    			auto c1 = c0;
    			auto c2 = c1;
    			auto c3 = c2;
    			c0();  // 10
    			c2();  // 10,虽然执行过一次c0(),且c2是c1的拷贝,c1是c0的拷贝,但两次拷贝时都没有对c0进行调用过,因此调用完c2,c2内部的i都是拷贝时的10
    			c3();  // 10
    			c3();  // 11
    			c2();  // 11,第二次调用c2会修改c2内部的i,因此i此时变成11
    			c1();  // 10
    			c0();  // 11
    			auto c4 = c3;
    			c4();     // 12,c4是c3的拷贝,拷贝时c3内i已经是11了,因此调用一次c4后c4内部的i会变成12
    			std::cout << "i=" << i << std::endl; // 9
    			std::cout << typeid(c0).name() << std::endl;  // 输出class ,证实lambda其实是一个类对象
    	    }
    		/* 注意:有人可能觉得值捕获多次调用lambda表达式并不会不断修改捕获的变量的副本,
    		因为似乎看起来每调用一次都是在局部区域修改变量的值,调用结束之后就出了作用域了,
    		比如两次调用c0()输出的结果似乎都应该是10,而不应该每次调用都会加1。
    		但实际上应该将每个lambda表达式看成一个闭包closure、一个对象,
    		每次调用c0()其实都是对同一个对象的操作,因此捕获变量的值每次调用都会产生变化。*/
    		
    		std::cout << "------------------------------" << std::endl;
    		
    		{
    			/* 按引用捕获会导致闭包包含对局部变量的引用,一旦由lambda创建的闭包越过了该局部变量的生命期,那么闭包内部的引用就会悬空 */
    			int i = 9;
    			auto c0 = [&i]()mutable{
    				std::cout << "i=" << ++i;
    				std::cout << ", lambda expression defined in: " << __func__ << ", " << __LINE__ << std::endl;
    			};
    			
    			/* 引用捕获则各个拷贝共同修改被捕获变量 */
    			decltype(c0) c1 = c0;
    			decltype(c1) c2 = c1;
    			decltype(c2) c3 = c2;
    			c0();  // 10
    			c2();  // 11
    			c3();  // 12
    			c3();  // 13
    			c2();  // 14
    			c1();  // 15
    			std::cout << "i=" << i << std::endl; // 15
    			std::cout << typeid(c0).name() << std::endl;
    	    }
    		
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    输出:

    Testing lambda......
    i=10, lambda expression defined in: operator (), 226
    i=10, lambda expression defined in: operator (), 226
    i=10, lambda expression defined in: operator (), 226
    i=11, lambda expression defined in: operator (), 226
    i=11, lambda expression defined in: operator (), 226
    i=10, lambda expression defined in: operator (), 226
    i=11, lambda expression defined in: operator (), 226
    i=12, lambda expression defined in: operator (), 226
    i=9
    class <lambda_afcedac3f7c7146c1c21461d632c0c31>
    ------------------------------
    i=10, lambda expression defined in: operator (), 255
    i=11, lambda expression defined in: operator (), 255
    i=12, lambda expression defined in: operator (), 255
    i=13, lambda expression defined in: operator (), 255
    i=14, lambda expression defined in: operator (), 255
    i=15, lambda expression defined in: operator (), 255
    i=15
    class <lambda_c3fd70843e0a34e585cc39112e403eb6>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3. lambda表达式捕获的都是局部变量

    一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
    捕获列表只用于局部非static变量。lambda可以直接使用局部static变量和它所在函数之外声明的名字。

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
        // str是函数局部非static变量,出现在捕获列表中,lambda表达式可以使用
        // std::cout是函数外定义的变量,可以在lambda中使用
        void printBigger(const std::string &str, const std::vector<std::string>& words){
            std::for_each(words.begin(), words.end(), [str](const std::string& it){
                if(it.size() > str.size()) std::cout << it << std::endl;});
        }
        
        auto main()-> void{
            std::cout << "test_lambda......." << std::endl;
            
            std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
            printBigger("abcd", strs);
                    
            std::cout << "test_lambda pass\n----------------------------" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    输出:

    test_lambda.......
    1234567890
    123456
    12345678
    test_lambda pass
    ----------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.1 企图使用未捕获的非静态局部变量会出错

    示例:

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
        void printBigger(const std::string &str, const std::vector<std::string>& words){
            int i = 0;
            std::for_each(words.begin(), words.end(), [str](const std::string& it){
                if(it.size() > str.size()) {
                    std::cout << it << std::endl;
                    std::cout << i++ << std::endl; // error: 'i' is not captured
                }});
        }
        
        auto main()-> void{
            std::cout << "test_lambda......." << std::endl;
            
            std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
            printBigger("abcd", strs);
                    
            std::cout << "test_lambda pass\n----------------------------" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3.2 可以直接使用未捕获的静态局部变量

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
        void printBigger(const std::string &str, const std::vector<std::string>& words){
            static int i = 0;   // 静态局部变量
            std::for_each(words.begin(), words.end(), [str](const std::string& it){  // 并未捕获i
                if(it.size() > str.size()) {
                    std::cout << it << std::endl;
                    std::cout << i++ << std::endl;
                
                }});
        }
        
        auto main()-> void{
            std::cout << "test_lambda......." << std::endl;
            
            std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
            printBigger("abcd", strs);
                    
            std::cout << "test_lambda pass\n----------------------------" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出:

    test_lambda.......
    1234567890
    0
    123456
    1
    12345678
    2
    test_lambda pass
    ----------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.3 可以主动捕获局部静态变量,但编译器会警告,因此不建议做多余的捕获

    示例:

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
        void printBigger(const std::string &str, const std::vector<std::string>& words){
            static int i = 0;  // 对于局部静态变量,如果主动捕获会有警告
            std::for_each(words.begin(), words.end(), [str,i](const std::string& it){  // warning: capture of variable 'i' with non-automatic storage duration
                if(it.size() > str.size()) {
                    std::cout << it << std::endl;
                    std::cout << i++ << std::endl;
                
                }});
        }
        
        auto main()-> void{
            std::cout << "test_lambda......." << std::endl;
            
            std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
            printBigger("abcd", strs);
                    
            std::cout << "test_lambda pass\n----------------------------" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出:

    test_lambda.......
    1234567890
    0
    123456
    1
    12345678
    2
    test_lambda pass
    ----------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4. lambda表达式定义了类型和对应的变量

    当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象: 传递的参数就是此编译器生成的类类型的未命名对象; 类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

    #include 
    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		auto f = [](){int a = 9; return 8;};
    
    		std::cout << typeid(f).name() << std::endl;     // [Visual Studio 15 2017] class 
    		std::cout << typeid(f()).name() << std::endl;   // int
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    Testing lambda......
    class <lambda_d95bb8f939d4d5414300050c2be20dce>
    int
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4

    5. 捕获

    类似函数传递,变量的捕获方式可以是值捕获或引用捕获。

    5.1 值捕获

    与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。
    示例:

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 999;
    		auto f = [i](){return i;};       // 被捕获的变量的值是在`lambda`创建时拷贝,而不是调用时拷贝。
    		i = 888;
    
    		std::cout << f() << std::endl;   // 999而不是888
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    Testing lambda......
    999
    ------------------------------
    
    • 1
    • 2
    • 3

    5.1.1 值捕获全局变量有点难以理解

    值捕获函数内部局部变量时不会修改局部变量的值,但是值捕获全局变量的时候会修改全局变量的值,原因未知。

    #pragma once
    
    #include 
    
    int gi = 2;
    
    namespace test_lambda
    {
        auto main()-> void{
            std::cout << "test_lambda......." << std::endl;
    
            int i = 2;
            auto lambd = [=]()mutable{std::cout << "i = " << ++i << ", gi = " << ++gi << std::endl;};
            lambd();
            
            // 此时i仍然是2,但是gi已经变成了3
            std::cout << "i = " << i << ", gi = " << gi << std::endl;
    
            std::cout << "test_lambda pass\n----------------------------" << std::endl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出:

    __cplusplus: 201703
    test_lambda.......
    i = 3, gi = 3
    i = 2, gi = 3
    test_lambda pass
    ----------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.1.2 值捕获会调用对象的拷贝构造函数

    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
    	using FuncVec = std::vector<std::function<int(void)>>;
    	
    	class Wiget
    	{
    	public:
    		Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
    		Wiget(const Wiget& wg):num_(wg.num_){std::cout << "Wiget copy-ctor called." << std::endl;}
    		~Wiget(){num_ = -1; std::cout << "~Wiget dtor called." << std::endl;}
    		std::function<int(void)> getfunc()const{
    				int copy_num = this->num_;  // 使用num_的拷贝,防止Wiget对象析构后继续使用成员带来的未定义行为
    				auto lambda = [copy_num]() -> int{
    				return copy_num;
    			};
    
    			return lambda;
    		}
    		void print()const{
    			std::cout << "num_ = " << num_ << std::endl;
    		}
    	private:
    		int num_;
    	};
    	
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    #if 0
    		FuncVec fv;
    		{
    			Wiget wg(5);
    			fv.emplace_back(wg.getfunc());
    			std::cout << "fv[0]() = " << fv[0]() << std::endl;
    		}   // 此时wg已经析构,但lambda中捕获的是局部变量copy_num,因此不会产生未定义行为
    		std::cout << "fv[0]() = " << fv[0]() << std::endl;
    #endif
    
    		Wiget wg1(8);
    		auto lambda = [wg1](){wg1.print();};
    		lambda();
    		
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    输出:

    Testing lambda......
    Wiget ctor called.
    Wiget copy-ctor called.   // 此处说明值捕获会对对象进行拷贝
    num_ = 8
    ~Wiget dtor called.
    ~Wiget dtor called.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.2 引用捕获

    以引用方式捕获变量时,在lambda函数体内使用的是引用所绑定的对象。
    当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。例如,1.绑定前变量已经定义;2.绑定后在函数体结束之后不能再继续使用;3.从函数返回lambda时此lambda不能包含引用捕获。

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 999;
    		auto f = [&i](){return i;};      // 引用捕获
    		i = 888;
    
    		std::cout << f() << std::endl;   // 888
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    Testing lambda......
    888
    ------------------------------
    
    • 1
    • 2
    • 3

    5.3 隐式捕获

    ellipse
    图1 lambda捕获列表

    5.3.1 隐式引用捕获

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		float j = 3.14f;
    		
    		// auto foo = [=]{i += 1; j = j * j;};  // error C3491: “i”: 无法在非可变 lambda 中修改通过复制捕获
                                                    // error C3491: “j”: 无法在非可变 lambda 中修改通过复制捕获
    
    		auto bar = [&]{i += 1; j = j * j;};
    		bar();
    		std::cout << i << ", \t" << j << std::endl;
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出:

    Testing lambda......
    10,     9.8596
    ------------------------------
    
    • 1
    • 2
    • 3

    5.3.2 混合使用隐式捕获、显式捕获

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		float j = 3.14f;
    		
    		auto foo = [=, &j]()->int{
    			j = j * j;
    			return i + 1;
    		};
    		
    		std::cout << foo() << std::endl;
    		std::cout << i << ", \t" << j << std::endl;
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    输出:

    Testing lambda......
    10
    9,      9.8596
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4

    5.3.3 混合使用隐式捕获、显式捕获时必须是真混合

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		float j = 3.14f;
    		
    		// =已经指定是值捕获了,j就不能再是值捕获,即第二位必须是引用捕获,前后必须不同
    		auto foo = [=, j]()->int{  // error C3489: 当默认捕获模式为按复制捕获时,需要“&j”(=)
    			j = j * j;
    			return i + 1;
    		};
    		
    		// &已经指定是引用捕获了,j就不能再是引用捕获,即第二位必须是值捕获,前后必须不同
    		auto bar = [&, &j]{};      // error C3488: 当默认捕获模式为按引用捕获时,无法显式捕获“&j”(&)
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    5.4 值捕获时使用mutable

    使用值捕获时lambda不会改变变量的值。当想在lambda函数体内部修改该变量的值但是又不想改变该变量在lambda外部的值的时候可以使用mutable。

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		
    		auto foo = [i]()mutable->int {return ++i;};
    		
    		std::cout << i << std::endl;         // 9
    		std::cout << foo() << std::endl;     // 10
    		std::cout << i << std::endl;         // 9
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出:

    Testing lambda......
    9
    10
    9
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.5 引用捕获时没必要使用mutable吧?

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		
    		auto foo = [&i]()->int {return ++i;};
    		// auto foo = [&i]()mutable->int {return ++i;};  // 结果和上句一样
    		
    		std::cout << i << std::endl;         // 9
    		std::cout << foo() << std::endl;     // 10
    		std::cout << i << std::endl;         // 10
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出:

    9
    10
    10
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4

    5.6 引用捕获时也不是说一定可以改变捕获变量的值

    引用捕获时能不能改变捕获变量的值取决于捕获的对象是const还是非const。
    引用捕获非const示例:

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		
    		auto foo = [&i]()->int {return ++i;};
    		
    		i = 0;
    		std::cout << foo() << std::endl;     // 1
    		std::cout << i << std::endl;         // 1
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出:

    Testing lambda......
    1
    1
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4

    引用捕获const示例:

    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		const int i = 9;
    		
    		auto foo = [&i]()->int {return ++i;};  // error C3491: “i”: 无法在非可变 lambda 中修改通过复制捕获
    
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.7 值捕获与引用捕获示例

    示例1:

    #pragma once
    
    #include 
    #include 
    
    namespace test_lambda
    {
        auto test0()-> void
        {
            int id = 0;
            auto lambda0 = [id]()mutable{   // 此处的id跟上句不是同一个,可以看作副本
                std::cout << "id: " << id << std::endl;
                ++id;
            };
            
            id = 42;     // 此处修改id的值并不会修改上面lambda0中id的值,因为二者不是同一个变量
            lambda0();   // 0,多次调用修改的是同一个变量id
            lambda0();   // 1,多次调用修改的是同一个变量id
            lambda0();   // 2,多次调用修改的是同一个变量id
            std::cout << "id: " << id << std::endl;  // 42,lambda0中是值传递,修改lambda0内id的副本不会改变外部id的值
        }
        
        auto test1()-> void
        {
            int id = 0;
            auto lambda1 = [&id](){  // 传引用,lambda1内外部会联动,因此修改lambda1内部的id即是修改外部的id,二者是同一个变量id
                std::cout << "id: " << id << std::endl;
                ++id;
            };
            
            id = 42;
            lambda1();   // 42
            lambda1();   // 43
            lambda1();   // 44
            std::cout << "id: " << id << std::endl;  // 45
        }
    
        auto main()-> void
        {
            std::cout << "test_lambda......." << std::endl;
            
            test0();
            std::cout << std::string(20,'-') << std::endl;
            test1();
    
            std::cout << "test_lambda pass" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    输出:

    test_lambda.......
    id: 0
    id: 1
    id: 2
    id: 42
    --------------------
    id: 42
    id: 43
    id: 44
    id: 45
    test_lambda pass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    示例2

    #pragma once
    
    #include 
    #include 
    
    namespace test_lambda
    {
        auto main()-> void
        {
            std::cout << "test_lambda......." << std::endl;
            
            int a = 1, b = 1, c = 1;
            auto m1 = [a, &b, &c]()mutable{
                auto m2 = [a, b, &c]()mutable{
                    std::cout << a << b << c << std::endl;
                    a = 4; b = 4; c = 4;
                };
                a = 3; b = 3; c = 3;
                m2();
            };
            
            a = 2; b = 2; c = 2;
            m1();
            std::cout << a << b << c << std::endl;
    
            std::cout << "test_lambda pass" << std::endl;        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出:

    test_lambda.......
    123
    234
    test_lambda pass
    
    • 1
    • 2
    • 3
    • 4

    执行m1(),m1()内部执行m2();
    m2()的c是pass by reference,因此其最终值取决于m2()内的赋值,c=4;
    m2()的b是pass by value,因此其最终值取决于m1()内的赋值,b=3;
    a在m1(),m2()内都是pass by value,因此其最终值取决于main()内的赋值,a=2。

    6. lambda的返回类型

    可以主动指定lambda的返回类型,也可以让编译器自动推断。
    注意:此处实测跟C++ Primer中文版P353页中的表述不一致,建议以实测为准。书中是说如果一个lambda体包含return之外的任何语句则编译器假定lambda返回void,(说实话,没看懂书上说的是啥意思)。

    #include 
    #include 
    
    namespace test_lambda
    {
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		int i = 9;
    		
    		auto foo = [i](){   // 未指定返回类型,自动推断为int
    			if(i >= 0){
    				return 1;
    			}
    			else{
    				return -1;
    			}
    		};
    		
    		auto bar = [i]()->float{
    			if(i >= 0){
    				return 1;
    			}
    			else{
    				return -1;
    			}
    		};		
    		
    		std::cout << typeid(foo()).name() << std::endl;
    		std::cout << typeid(bar()).name() << std::endl;
    		
    		std::cout << "------------------------------" << std::endl;
    
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    输出:

    Testing lambda......
    int
    float
    ------------------------------
    
    • 1
    • 2
    • 3
    • 4

    7. Avoid default capture modes

    在对象中捕获this时小心dangle。

    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
    	using FuncVec = std::vector<std::function<int(void)>>;
    	
    	class Wiget
    	{
    	public:
    		Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
    		Wiget(const Wiget& wg):num_(wg.num_){std::cout << "Wiget copy-ctor called." << std::endl;}
    		~Wiget(){num_ = -1; std::cout << "Wiget dtor called." << std::endl;}
    		std::function<int(void)> getfunc()const{
    			// 写法1,编译通过,但行为可能未定义
    			auto lambda = [this]() -> int{
    				//std::cout << this->num_ << std::endl;
    				return this->num_;
    			};
    #if 0       // 写法2,编译通过,但行为可能未定义
    			auto lambda = [=]() -> int{
    				std::cout << this->num_ << std::endl;
    				return this->num_;
    			};
    #endif			
    #if 0       // 写法3,编译错误
    			auto lambda = []() -> int{  // error C4573: “test_lambda::Wiget::num_”的用法要求编译器捕获“this”,但当前默认捕获模式不允许使用“this”
    				std::cout << num_ << std::endl;
    				return num_;
    			};
    #endif
    #if 0       // 写法4,编译错误
    			auto lambda = [num_]() -> int{  // error C3480: “test_lambda::Wiget::num_”: lambda 捕获变量必须来自封闭函数范围
    				std::cout << num_ << std::endl;
    				return num_;
    			};
    #endif
    
    			return lambda;
    		}
    	private:
    		int num_;
    	};
    	
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		FuncVec fv;
    		{
    			Wiget wg(5);
    			fv.emplace_back(wg.getfunc());
    			std::cout << "fv[0]() = " << fv[0]() << std::endl;
    		}   // 此时wg已经析构,this已经悬空dangle,lambda中继续使用之前捕获的num_已经是未定义行为
    		// 未定义行为意味着今天正确明天可能会出错,未定义行为不一定表示立即会崩溃或出错,跟编译器、编译选项、代码细节相关
    		// 很多时候开优化出错,也是因为未定义行为容易在开启优化的时候更容易暴露出来
    		std::cout << "fv[0]() = " << fv[0]() << std::endl;
    		
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    输出:

    Testing lambda......
    Wiget ctor called.
    fv[0]() = 5
    Wiget dtor called.
    fv[0]() = -1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解决方法:

    #include 
    #include 
    #include 
    
    namespace test_lambda
    {
    	using FuncVec = std::vector<std::function<int(void)>>;
    	
    	class Wiget
    	{
    	public:
    		Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
    		~Wiget(){num_ = -1; std::cout << "~Wiget dtor called." << std::endl;}
    		std::function<int(void)> getfunc()const{
    				//std::cout << "num_=" << this->num_ << std::endl;
    				int copy_num = this->num_;  // 使用num_的拷贝,防止Wiget对象析构后继续使用成员带来的未定义行为
    				auto lambda = [copy_num]() -> int{
    				//std::cout << "copy_num=" << copy_num << std::endl;
    				return copy_num;
    			};
    
    			return lambda;
    		}
    	private:
    		int num_;
    	};
    	
    	auto main() -> int
    	{
    		std::cout << "Testing lambda......" << std::endl;
    
    		FuncVec fv;
    		{
    			Wiget wg(5);
    			fv.emplace_back(wg.getfunc());
    			std::cout << "fv[0]() = " << fv[0]() << std::endl;
    		}   // 此时wg已经析构,但lambda中捕获的是局部变量copy_num,因此不会产生未定义行为
    		std::cout << "fv[0]() = " << fv[0]() << std::endl;
    		
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    输出:

    Testing lambda......
    Wiget ctor called.
    fv[0]() = 5
    ~Wiget dtor called.
    fv[0]() = 5
    
    • 1
    • 2
    • 3
    • 4
    • 5

    8. 泛型lambda表达式(C++14)

    #include 
    #include 
    #include 
    #include    // std::less
    
    struct Obj
    {
        int id_;
        std::string color_;
    };
    
    auto main()->int {
        auto less_obj = [](auto&& x, auto&& y) {
            return std::forward<decltype(x)>(x) < std::forward<decltype(y)>(y);
        };
    
        std::cout << less_obj(3, 4) << std::endl;   // 1
        std::cout << less_obj(13, 4) << std::endl;  // 0
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    uniapp实现拍照涂鸦
    使用HTML制作静态网站:传统文化戏剧锡剧带psd设计图(2个页面)
    SegNet学习笔记
    std::variant 源码分析
    linux centos7 安装nginx
    前端程序员兼职副业平台推荐
    pytorch深度学习单卡训练和多卡训练
    分享从零开始学习网络设备配置--任务3.7 使用动态路由RIPv2实现网络连通
    LabVIEW定义自定义错误代码在应用程序中使用
    SpringBoot自动配置原理
  • 原文地址:https://blog.csdn.net/liugan528/article/details/126354737