匿名函数
匿名函数更通用的称呼是 Lambda 表达式,C++ 的 Lambda 表达式语法如下:
[/* 捕获列表 */](/* 函数参数 */){
// 函数体
}
Lambda 表达式是一个 右值,不能被取地址。由于没有名字,一般也无法进行递归。
使用 Y 组合子可以进行递归。Y 组合子是 Lambda 演算的一部分。 |
捕获列表是以逗号分隔的变量列表,例如:
[a, &b](){} (1)
[&](){} (2)
[=](){} (3)
[this](){} (4)
1 | 使用拷贝的形式捕获 a,使用引用的方式拷贝 b |
2 | 使用引用捕获当前作用域的所有变量 |
3 | 使用拷贝捕获当前作用域内的所有变量 |
4 | 捕获 this。Lambda 中可以直接使用成员变量而无需 this→ |
当捕获列表为空时,Lambda 表达式的行为与普通的函数指针别无二致,可以当作函数指针类型的形参。一旦捕获列表不为空,Lambda 就变为了一个 闭包 ,行为更接近于重载了 operator()
的对象。但无论是哪一种,都可以使用 std::functional
访问。
无捕获列表的 Lambda 表达式暂且不提,这种形式比较简单,没什么值得注意的,下面谈论闭包。
闭包需要注意的地方有两点:
捕获的引用生命周期不能比闭包结束的晚
捕获的共享指针的生命周期会和闭包一样长
第一种情况最容易出错在 捕获了 this 的闭包 使用了移动语义。例如:
class A {
public:
A(): func_([this](){ (1)
// do somthing
}){}
A(A&& b){
func_ = std::move(b.func_); (2)
// ...
}
private:
std::function<void(void)> func_;
};
1 | 代码首先在此处捕获了 this |
2 | 代码在此处将 b 的闭包传递给自己。但是此闭包中捕获的 this 指向的是 b,因而导致闭包的生命周期晚于 this 的生命周期。 |
第二种情况一般被用作内存管理:将智能指针传递给闭包,从而让闭包自动释放内存:
class Session: public std::enable_shared_this<Session>{
public:
void read(){
if(!socket_.is_open()) return; (1)
auto self = enable_shared_this();
socket_.async_read(asio::buffer(), [self, this](size_t n){ (2)
if(n<=0) socket_.close();
read();
})
}
private:
asio::socket socket_;
};
// ...
(new Session)->read(); (3)
1 | 当套接字关闭时退出 |
2 | 将 self 捕获到闭包中 |
3 | 创建对象后立即抛弃它,不在持有它的左值 |
在上面的代码中,创建一个 Session 并调用 self 后就不需要管了,因为 read 创建了一个调用,且每次循环调用都会将自身的智能指针捕获到闭包中,从而防止被析构。当套接字关闭时,闭包结束,self 作为最后一个持有 this 的智能指针也被析构,从而自动完成了内存管理。