C++ Lambda闭包的思考
本来想研究golang中的闭包的,但是不知怎么思路就跑到这来了……
关于闭包的概念,我认为这里讲解的最好:
闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义:在实现深约束(注)时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。
早在C++11开始就支持了Lambda闭包,使用的姿势大概是这样的
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> std::function<void()> getClosure() { int v = 0; return [v]() mutable { std::cout << v++ << std::endl; }; } int main() { auto c = getClosure(); c(); // 0 c(); // 1 } |
与我们所常听到的javascript的闭包别无二致,局部变量v可以在其作用域外被访问,实现了累加的效果。但是到这里我们不禁要问一个问题:C++函数中的变量(除了new出来的)都是分配在栈上的,但是函数退出后栈空间被销毁,那么这个v在哪?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <cstdio> #include <iostream> std::function<void()> getClosure() { int v = 0; printf("%p %d\n", &v, v); // 打印函数中v的地址 return [v]() mutable { printf("%p %d\n", &v, v++); // 打印闭包中v的地址 }; } int main() { auto c = getClosure(); c(); c(); } /*输出 0x7ffee54b36ec 0 0x7ffee54b3728 0 0x7ffee54b3728 1 */ |
OK,我们发现v被移动了位置,lambda中的v已不是原来栈上那个v了。如果我们连同lambda函数的地址也打印出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <cstdio> #include <iostream> std::function<void()> getClosure() { int v = 0; return [v]() mutable { printf("v's address in closure: %p\n", &v); // 打印闭包中v的地址 }; } int main() { auto c = getClosure(); c(); printf("c's address: %p\n", &c); printf("c's size: %lu\n", sizeof(c)); } /*输出 v's address in closure: 0x7ffeedd1a728 c's address: 0x7ffeedd1a720 c's size: 48 */ |
会发现v其实是跟lambda函数绑定的。既然变量跟lambda是绑定的,那如果复制了lambda会怎样?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> std::function<void()> getClosure() { int v = 0; return [v]() mutable { std::cout << v++ << std::endl; }; } int main() { int v = 0; auto f = getClosure(); auto g = f; f(); // 0 f(); // 1 g(); // 0 g(); // 1 } |
正如所想,lambda的复制会直接拷贝环境的参数列表。到这里我们已经认识到C++的闭包是一个语法糖了,正因为如此才要在lambda里指定参数列表,好让编译器知道哪些局部变量是闭包所依赖的上下文环境,不需要将栈上的变量转移到堆上去。但是对于C++如果要实现真正的闭包是有点丑陋的:想象编译器需要把局部变量用智能指针包起来,匿名函数要被“释放”才能释放相应的上下文,这都什么鬼。。。
既然我们知道c++闭包的这个性质,将就用就是了。但是我又产生了一个问题:lambda复制的时候,参数的拷贝是深拷贝吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> struct Inner { int v; }in{100}; struct Outer { int v; Inner *t; }; std::function<void()> getClosure() { Outer out{0, &in}; return [out]() mutable { std::cout << out.v++ << "\t" << out.t->v++ <<std::endl; }; } int main() { int v = 0; auto f = getClosure(); auto g = f; f(); // 0 100 f(); // 1 101 g(); // 0 102 g(); // 1 103 } |
是浅拷贝: )