C/C++语言中闭包的探究及比较
2013-09-10 16:34
337 查看
Lee发表评论阅读评论20,062
人阅读
(感谢投稿人 @思禽饮霜 )
这里主要讨论的是C语言的扩展特性block。该特性是Apple为C、C++、Objective-C增加的扩展,让这些语言可以用类Lambda表达式的语法来创建闭包。前段时间,在对CoreData存取进行封装时(让开发人员可以更简洁快速地写相关代码),我对block机制有了进一步了解,觉得可以和C++
11中的Lambda表达式相互印证,所以最近重新做了下整理,分享给大家。
0. 简单创建匿名函数
下面两段代码的作用都是创建匿名函数并调用,输出Hello, World语句。分别使用Objective-C和C++ 11:在创建闭包(或者说Lambda函数)的语法上,Objective-C采用的是上尖号^,而C++ 11采用的是配对的方括号[]。
不过“匿名函数”一词是针对程序员而言的,编译器还是采取了一定的命名规则。
比如下面Objective-C代码中的3个block,
1. 从语法上看如何捕获外部变量
在上面的代码中,已经看到“匿名函数”可以直接访问外围作用域的变量i:这一段代码可以成功输出i的值。
我们把一样的逻辑搬到C++上:
以BNF来表示Lambda表达式的上下文无关文法,存在:
2. 从语法上看如何修改外部变量
上面代码中使用了符号=,通过拷贝方式捕获了外部变量i。但是如果尝试在Lambda表达式中修改变量i:
在C++的闭包语法中,如果需要对外部变量的写权限,可以使用符号&,通过引用方式捕获:
为什么呢?请继续往下看 :)
3. 从实现上看如何捕获外部变量
闭包对于编程语言来说是一种语法糖,包括Block和Lambda,是为了方便程序员开发而引入的。因此,对Block特性的支持会落地在编译器前端,中间代码将会是C语言。先看如下代码会产生怎样的中间代码。
第二、三个成员是标志位和保留位。
第四个成员是对应的“匿名函数”,在这个例子中对应函数:
而struct __main_block_impl_0的结构如下:
结构体__main_block_impl_0又引入了一个新的结构体,也是中间代码里最后一个结构体:
最后剩下main函数对应的中间代码:
其中,局部变量i是以值传递的方式拷贝一份,作为__main_block_impl_0的构造函数的参数,并以初始化列表的形式赋值给其成员变量i。所以,基于这样的实现,不允许直接修改外部变量是合理的——因为按值传递根本改不到外部变量。
4. 从实现上看如何修改外部变量(__block类型指示符)
如果想要修改外部变量,则需要用__block来修饰:代码中blk对应的结构体也发生了变化:
对应的函数也不同了:
这里没有看__main_block_desc_0发生的变化,放到后面讨论。
使用__block类型指示符的本质就是引入了__Block_byref_{$var_name}_{$index}结构体,而被__block关键字修饰的变量就被放到这个结构体中。另外,block结构体通过引入__Block_byref_{$var_name}_{$index}指针类型的成员,得以间接访问到外部变量。
通过这样的设计,我们就可以修改外部作用域的变量了,再一次应了那句话:
There is no problem in computer science that can’t be solved by adding another level of indirection.
指针是我们最经常使用的间接手段,而这里的本质也是通过指针来间接访问,为什么要特地引入__Block_byref_{$var_name}_{$index}结构体,而不是直接使用int *来访问外部变量i呢?
另外,__Block_byref_{$var_name}_{$index}结构体中的__forwarding指针成员有何作用?
请继续往下看 :)
5. 背后的内存管理动作
在Objective-C中,block特性的引入是为了让程序员可以更简洁优雅地编写并发代码(配合看起来像敏感词的GCD)。比较常见的就是将block作为函数参数传递,以供后续回调执行。先看一段完整的、可执行的代码:
正常情况下,这段代码可以成功运行,输出:
5.1 拷贝block结构体
上文提到block结构体__block_impl的第一个成员是isa指针,使其成为NSObject的子类,所以我们可以通过相应的内存管理机制将其拷贝到堆上:
5.2 拷贝捕获的变量(__block变量)
在拷贝block结构体的同时,还会将捕获的__block变量,即结构体__Block_byref_i_0,复制到堆上。这个任务落在前面没有讨论的__main_block_desc_0结构体身上:
5.3 __forwarding指针的作用
当复制动作完成后,栈上和堆上都存在着__main_block_impl_0结构体。如果栈上、堆上的block结构体都对捕获的外部变量进行操作,会如何?
下面是一段示例代码:
test()函数中的blk结构体位于栈中,在休眠1s后被执行,对i进行自增动作。
testBlock函数在休眠2s后,执行位于堆上的block结构体,这里为demoBlk。
上述代码执行后输出:
这就是前面提到的__forwarding指针成员的作用了:
起初,栈上的__block变量的成员指针__forwarding指向__block变量本身,即栈上的__Block_byref_i_0结构体。
当__block变量被复制到堆上后,栈上的__block变量的__forwarding成员会指向堆上的那一份拷贝,从而保持一致。
参考资料:
http://msdn.microsoft.com/en-us/library/dd293603.aspxhttp://www.cprogramming.com/c++11/c++11-lambda-closures.html
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html
http://en.wikipedia.org/wiki/Closure_(computer_science)
相关文章推荐
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C/C++语言中闭包的探究及比较
- C语言中闭包的探究及比较
- OOP语言技术比较:Java,C++,Object Pascal
- java,c,c++ 语言之间基本数据类型的比较
- 强弱类型,动态静态语言比较(JAVA,C,C++,Python,Ruby,PHP,Perl)
- 六种流行的语言---C、C++、python、Java、php、C#比较[转]
- java,c,c++ 语言之间基本数据类型的比较
- FORCAL与C/C++、MATLAB、Python、Lua等各种语言的速度比较
- java,c,c++ 语言之间基本数据类型的比较
- Lu与C/C++、Forcal、MATLAB、Python、Lua等各种语言的速度比较