lambda表达式
学习 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实现模拟初始化捕获。