闭包(closure)是函数式编程中的概念,指新生成的函数对象离开初始作用域后仍然“携带”原来作用域里的变量。 用一个比较经典的例子,是阮一峰老师翻译的《黑客与画家》提到过的(阮一峰老师的博客上写到过):设计一个工厂函数,它接受一个值n,返回一个累加器,这个累加器以n为初始值,每次接受一个值i,将存储的值加上i后返回。
作者为了说明 Lisp 语言的先进性列举了很多语言中对此的实现,我觉得其中 JavaScript 的代码最好懂:
1 | function foo (n) { |
对作用域很敏感的人可能要问了:foo
返回的匿名函数的函数体中使用的n
在出foo
后就失效了啊,怎么还能加呢?其实这个n
是foo
中最外层n
的一个副本,所属权是那个匿名函数,它的内存由GC负责释放。更棒的是,每次生成的匿名函数持有的n是不同的变量,互不干扰。
这样可以看出“闭包”这个名字很形象,函数“包”着上一个作用域中的变量到其它地方去。 在那篇文章里又说
其他语言怎么样?前文曾经提到过Fortran、C、C++、Java和Visual Basic,看上去使用它们,根本无法解决这个问题。
这么评价C++是不公正的,C++11前虽然没有函数字面量,但是你可以定义一个带operator()
方法的类,然后类可以有成员,这样就能实现要求的效果,代码如下:
1 | template <typename T> |
美中不足的是C++11前虽然能在函数中定义类,但是函数的返回类型要提前声明,所以只能在函数外定义Counter
类。
C++11给我们带来了lambda函数和function模板,代码就可以变成这样:
1 |
|
注意我们声明了在返回的lambda中n是按值绑定的。但这段代码其实不能通过编译……因为C++中lambda函数的按值绑定默认是不可变的。 当然我们还可以这样写:
1 |
|
这样不会给foo
所在命名空间中增加一个类,也能满足泛型,但这样Paul Graham先生就会笑话我们写的不够简洁,还要手动添加闭包涉及的变量…… 幸好C++11是有解决办法的,能成功编译运行的代码如下:
1 |
|
这个mutable关键字恐怕是用的最少的C++关键字,有兴趣的可以查查它的用处。在这里它就是用于让按值绑定的变量在lambda函数体中可变。其实lambda的实现就是编译器帮你生成一个含相应成员的匿名类,再生成它的一个对象,所以不用担心没有GC的C++怎么回收lambda闭包里携带的变量。
主要归功于C++11,可以说让C++变得焕然一新。