C++ Lambda闭包的思考

01. 12月 2017 C++ 0

本来想研究golang中的闭包的,但是不知怎么思路就跑到这来了……

关于闭包的概念,我认为这里讲解的最好:

闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义:在实现深约束()时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。

早在C++11开始就支持了Lambda闭包,使用的姿势大概是这样的

#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在哪?

#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函数的地址也打印出来:

#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会怎样

#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复制的时候,参数的拷贝是深拷贝吗

#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
}

是浅拷贝: )


发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据