Home Page

学习 Effective Modern C++ 第6章 笔记

避免lambda表达式的默认捕获模式

C++11有两种默认捕获模式:按引用与按值。

按引用捕获的危险情况

std::function 头文件 #include < functional >

using FilterContainer = std::vector<std::function<bool(int)>>; //类似于typedef
FilterContainer filters;

void Test()
{
	int value  = 10;
	filters.push_back([&value](int num)->bool
	{
		return num%value == 0;
	});
}

在上面的例子中,使用vector来存储std::function, 而std::function存储形参参数为int类型的返回值为bool类型的函数。value的作用域为Test函数内,当跳出Test函数,value也就不复存在了。而filters中还存在引用了该值的lambda函数,而当用到value这个值的时候程序还会去value以前的地址读取数值。而这个地址已经不属于该程序了,这样会造成读取失败或者读取到一个未知的数值。
以上的引用危险情况可以使用按值捕获的方法避免危险情况的发生。这样lambda会保留一个value的副本。

按值捕获
void Test()
{
	int value  = 10;
	filters.push_back([value](int num)->bool
	{
		return num%value == 0;
	});
}

按值引用的危险情况

虽然按值捕获有时可以避免按引用捕获的危险情况,但当捕获的对象是指针时,就会有危险情况的发生。按值捕获指针只是保留了一份指针的副本,并不能保证该指针是否会在函数外被delete。

在默认捕获情况下[=], 并不会捕获static修饰的变量,这些变量可以被lambda表达式访问,但他们并没有被捕获。

void Test(std::function<bool(int)>& fun)
{
	static int value = 10;
	fun = [=](int num)->bool { //此处并没有在lambda表达式中创建value的副本,value并未被捕获
		return num % value == 0;
	};
	++value; //对value操作会影响lambda表达式中的value值
}

int main()
{
	std::function<bool(int)> fun;
	Test(fun);
	std::cout << fun(20) << std::endl; //此处输出0,因为现在lambda表达式中的value = 11;
	return 0;
}

要点速记

1.引用的默认捕获会导致空悬指针问题
2.按值的默认捕获极易受空悬指针影响(尤其是this,当在类非静态函数中进行默认按值捕获时会将this指针捕获,
如果对象被析构就会造成空悬指针),并会误导人们认为lambda式是自洽(与lambda表达式外的数据变化绝缘)的。

使用初始化捕获将对象移入闭包

C++14 中使用初始化捕获将对象移入闭包

class Student 
{
public:
	Student() {}
	std::string GetName() { return this->name; }
private:
	std::string name = "lihua";
	std::string sex = "man";
};
int main()
{
	auto spStu = std::make_unique<Student>();
	auto fp = [spStus = std::move(spStu)]()
	{
		std::cout << spStus->GetName() << std::endl;
	};
	fp();
	return 0;
}

上述代码,使用了C++14的初始化捕获的模式,使用移动语句将spStu的内部指针所有权转移(使用std::move函数并不会发生内存操作,只是所有权的转移)给lambda中的一个新变量spStus。

C++11 中模拟初始化捕获

使用std::bind来生成函数对象,std::bind返回的是可执行函数,可以使用std::function类型来进行接收。
std::bind的使用具体可以查看这篇文章C++11中的std::bind

auto spStu = std::make_unique<Student>();
auto fp_11 = std::bind([](const std::unique_ptr<Student>& sp) {
		std::cout << sp->GetName() << std::endl;
		}, std::move(spStu));
fp_11();

通过std::bind提前绑定参数,实现C++14中的初始化捕获。

要点速记

1.使用C++14中的初始化捕获将对象一如闭包。
2.在C++11中,通过手动实现std::bind实现模拟初始化捕获。

Q.E.D.