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 <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <typeinfo>

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

输出:

Testing lambda......
8
int
1
123
123456
12345678
1234567890
------------------------------

2. lambda表达式的本质

#include <iostream>
#include <typeinfo>

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_afcedac3f7c7146c1c21461d632c0c31>,证实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;
	}
}

输出:

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>

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

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

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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;        
    }
}

输出:

test_lambda.......
1234567890
123456
12345678
test_lambda pass
----------------------------

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

示例:

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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;        
    }
}

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

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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;        
    }
}

输出:

test_lambda.......
1234567890
0
123456
1
12345678
2
test_lambda pass
----------------------------

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

示例:

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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;        
    }
}

输出:

test_lambda.......
1234567890
0
123456
1
12345678
2
test_lambda pass
----------------------------

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

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

#include <iostream>
#include <typeinfo>

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 <lambda_d95bb8f939d4d5414300050c2be20dce>
		std::cout << typeid(f()).name() << std::endl;   // int

		std::cout << "------------------------------" << std::endl;

		return 0;
	}
}

输出:

Testing lambda......
class <lambda_d95bb8f939d4d5414300050c2be20dce>
int
------------------------------

5. 捕获

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

5.1 值捕获

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

#include <iostream>

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;
	}
}

输出:

Testing lambda......
999
------------------------------

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

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

#pragma once

#include <iostream>

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;
    }
}

输出:

__cplusplus: 201703
test_lambda.......
i = 3, gi = 3
i = 2, gi = 3
test_lambda pass
----------------------------

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

#include <iostream>
#include <vector>
#include <functional>

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;
	}
}

输出:

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

5.2 引用捕获

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

#include <iostream>

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;
	}
}

输出:

Testing lambda......
888
------------------------------

5.3 隐式捕获

ellipse
图1 lambda捕获列表

5.3.1 隐式引用捕获

#include <iostream>

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;
	}
}

输出:

Testing lambda......
10,     9.8596
------------------------------

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

#include <iostream>

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;
	}
}

输出:

Testing lambda......
10
9,      9.8596
------------------------------

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

#include <iostream>

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;
	}
}

5.4 值捕获时使用mutable

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

#include <iostream>

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;
	}
}

输出:

Testing lambda......
9
10
9
------------------------------

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

#include <iostream>

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;
	}
}

输出:

9
10
10
------------------------------

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

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

#include <iostream>

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;
	}
}

输出:

Testing lambda......
1
1
------------------------------

引用捕获const示例:

#include <iostream>

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;
	}
}

5.7 值捕获与引用捕获示例

示例1:

#pragma once

#include <iostream>
#include <string>

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;        
    }
}

输出:

test_lambda.......
id: 0
id: 1
id: 2
id: 42
--------------------
id: 42
id: 43
id: 44
id: 45
test_lambda pass

示例2

#pragma once

#include <iostream>
#include <string>

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;        
    }
}

输出:

test_lambda.......
123
234
test_lambda pass

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

6. lambda的返回类型

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

#include <iostream>
#include <typeinfo>

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;
	}
}

输出:

Testing lambda......
int
float
------------------------------

7. Avoid default capture modes

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

#include <iostream>
#include <vector>
#include <functional>

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;
	}
}

输出:

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

解决方法:

#include <iostream>
#include <vector>
#include <functional>

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;
	}
}

输出:

Testing lambda......
Wiget ctor called.
fv[0]() = 5
~Wiget dtor called.
fv[0]() = 5

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

#include <iostream>
#include <set>
#include <string>
#include <functional>   // 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值