苹果GNU C/C++,objective-C/C++新特性:Blocks

来源:互联网 发布:c语言 volatile 含义 编辑:程序博客网 时间:2024/05/16 05:16
 现在外面流行一个词,叫“多核”,呵呵。由于现在硬件工艺达到了饱和,所以很多处理器制造商想从多核来进一步发掘处理器的性能。
而Apple现在 也引领了时尚,对XCode 3.2中的GCC 4.2扩充了Blocks新的语法特性,使其能够有助于多核应用的开发。
其中,Apple在 Snow Leopard中所用到的Grand Central Dispatch(GCD)就是基于Blocks实现的。关于Blocks以及GCD在苹果官方的介绍,请见:Introducing Blocks and Grand Central Dispatch。

下面先介绍一下编译器的设置,使其能够认得Blocks 语法。
首先,打开XCode3.2中菜单项Project中的Edit Project Settings。
在 Architectures大栏中的Base SDK项,我们要选择Mac OS X 10.6。
然后看Compiler Version这一栏,我们要将C/C++ Compiler Version项选择为GCC4.2。
最后,在GCC4.2 Language这一栏中,我们要找到C Language Dialect这一项,把它设为GNU99。
这样,编译环境的配制就完成了。

下面我们将开始介绍 Blocks
首先看一段最简单的例子:
  1. int main(void)
  2. {
  3.     void (^blocks)(void) = ^(void){ puts("Hello, world!"); };
  4.     blocks();
  5. }


首先,声明了一个变量void(^blocks)(void),表示一个指向void(void)的函数块的引用。而^(void){ puts("Hello, world!"); };则表示一个函数块,它接受void类型参数,并且返回void。其功能是输出Hello, world!。
下面的blocks();就是通过这个函数块的引用来调用函数。所以,这段代码的运行结果就是输出Hello, world!。

Blocks 比C++0x中的λ表达式更强的一点是,它可以是个数组类型:

  1. int main(void)
  2. {
  3.     void (^p[2])(void) = { ^(void){ puts("Hello, world!"); }, ^(void){ puts("Goodbye!"); } };
  4.     p[0](), p[1]();
  5. }


这 里p的类型为void(^[2])(void),表示含有2个void(^)(void)块引用元素的变量。

下面谈谈函数块对其外部临时 变量的可访问情况。

复制代码
  1. static int global = 100;
  2.  
  3. int main(void)
  4. {
  5.     int local = 200;
  6.     void (^p)(void) = ^(void){ printf("The answer is: %dn", global + local); };
  7.     p();
  8. }


对 于FP比较熟悉的朋友可能会想到,如果一个外部变量能够随随便便被一个函数块修改的话,那么对于其本身的副作用仍然无法进行方便地多核并行编程。
那 么我们不妨试试看吧:

  1. static int global = 100;
  2.  
  3. int main(void)
  4. {
  5.     int local = 200;
  6.     void (^p)(void) = ^(void){
  7.         printf("The answer is: %dn", global + local);
  8.         global++;
  9.         local--;    // compiling error:error: decrement of read-only variable \'local\'
  10.     };
  11.     p();
  12.     printf("After modified, global is: %d and local is %dn", global, local);


对 于全局变量可以进行修改,但是对于main函数中的局部变量则不行。如果对local修改则无法通过编译。很显然,Blocks对此已经有了相应的机制。 那么我们如何能够对local进行修改呢?

  1. static int global = 100;
  2.  
  3. int main(void)
  4. {
  5.     __block int local = 200;
  6.     static int s = 10;
  7.     
  8.     void (^p)(void) = ^(void){
  9.         printf("The answer is: %dn", global + local);
  10.         global++;
  11.         s++;
  12.         local--;
  13.     };
  14.     p();
  15.     printf("After modified, global is: %d and local is %d and s is: %dn", global, local, s);
  16. }


这里引入了一个新的关键字 ——__block,用此声明一个局部变量可以被函数块修改。

我们再来讨论一个更高级的话题,Blocks是否可以递归?
如果要 使Blocks能够递归,那么在函数块中必须能够引用函数块的入口地址。我做了一些尝试,当函数块引用是全局的或static的,即函数块内所引用的函数 块引用变量的值在初始时就已经确定的,那么可以使用递归。

  1. int main(void)
  2. {
  3.     void (^p)(int) = 0;
  4.     static void (^ const blocks)(int) = ^(int i){ if(i > 0){ puts("Hello, world!"); blocks(i - 1); } };
  5.     p = blocks;
  6.     p(2);
  7. }


如 果在上述代码中将blocks前的static去掉,那么在运行时就会出错,因为blocks在被函数块引用时是未初始化值,所以调用它的话就访问了无效 地址,或者所要执行的指令是未定义的。
各位可以再做些尝试。

下面将详细地分析一下Blocks结合泛型的使用。
由于泛型能够使我们更高效、合理地管理好自己的代码,同时也为部件化提供了许多便利之处。那么 Blocks与泛型结合会产生什么新元素呢?
我们先举一个简单例子:

  1. #import <Foundation/Foundation.h>
  2.  
  3. template <void pBlock(void)>
  4. void BlockTest(void)
  5. {
  6.     pBlock();
  7. }
  8.  
  9. void Hi(void)
  10. {
  11.     NSLog(@"Hi, there!");
  12. }
  13.  
  14. int main(int argc, const char* argv[])
  15. {
  16.     BlockTest<Hi>();
  17. }


上 述代码中尚未出现Blocks,但是我们可以看到,一般的外部函数能够作为模板参数。
那么Blocks是否可以这么做呢?我们不妨尝试一下:

  1. #import <Foundation/Foundation.h>
  2.  
  3. template <void (^pBlock)(void)>
  4. void BlockTest(void)
  5. {
  6.     pBlock();
  7. }
  8.  
  9. int main(int argc, const char* argv[])
  10. {
  11.     BlockTest<^(void) { NSLog(@"Hi, there!"); }>();
  12. }


编 译时会在第11行出现error: no matching function for call to \'BlockTest()\'
C++标准 中明确指出,模板参数必须为常量表达式,如果是函数的话必须是带有外部连接(即external-linkage)的函数指针。而Blocks表达式首先 就不是一个常量表达式,然后它也没有外部连接。

我们下面看第二个例子:

  1. #import <Foundation/Foundation.h>
  2.  
  3. template <typename T>
  4. void BlockTest(void (&pBlock)(T))
  5. {
  6.     pBlock(T());
  7. }
  8.  
  9. static void Hi(int a)
  10. {
  11.     NSLog(@"The value is: %dn", a);
  12. }
  13.  
  14. int main(int argc, const char* argv[])
  15. {
  16.     BlockTest(Hi);
  17. }


上 述代码中使用了函数引用作为函数参数,然后由实参类型演绎出模板类型。这段代码将能正常地通过编译、连接并正常运行。
那么我们下面再看一看 Blocks是否具有这个泛型特性:

  1. #import <Foundation/Foundation.h>
  2.  
  3. template <typename T>
  4. void BlockTest(void (^pBlock)(T))
  5. {
  6.     pBlock(T());
  7. }
  8.  
  9. int main(int argc, const char* argv[])
  10. {
  11.     BlockTest(^(int a) { NSLog(@"The value is: %dn", a); });
  12. }


编 译后出现 error: no matching function for call to \'BlockTest(void (^)(int))\'
即 使显式地将<int>模板实参加上也没用。也就是说Blocks的参数类型包括返回类型不能是一个泛型。

好,我们再看第三个 例子:

  1. #import <Foundation/Foundation.h>
  2. #include <iostream>
  3. #include <typeinfo>
  4. using namespace std;
  5.  
  6. template <typename T>
  7. void BlockTest(T pBlock)
  8. {
  9.     pBlock();
  10.     cout << "The type is: " << typeid(T).name() << endl;
  11. }
  12.  
  13. static void Hi(void)
  14. {
  15.     NSLog(@"Hi, there!");
  16. }
  17.  
  18. int main(int argc, const char* argv[])
  19. {

 

 BlockTest(Hi);

  • }


      这段代码展示了整个函数指针类型演绎出模板实参。对于目前已被很多编译器所实现的Lambda表达式,这是与泛型挂钩的唯一桥梁,那么Blocks是否具备 这个特性呢?

      1. #import <Foundation/Foundation.h>
      2. #include <iostream>
      3. #include <typeinfo>
      4. using namespace std;
      5.  
      6. template <typename T>
      7. void BlockTest(T pBlock)
      8. {
      9.     pBlock();
      10.     cout << "The type is: " << typeid(T).name() << endl;
      11. }
      12.  
      13. int main(int argc, const char* argv[])
      14. {
      15.     BlockTest(^(void) { NSLog(@"Hi, there!"); });
      16. }


      恭 喜,我们成功了。这段代码能够正常编译和运行。各位可以自己看看输出结果。其中,类型信息是被压缩过的:F表示函数,P表示指针,v表示void类型。
      Blocks 与C++0x中的lambda表达式一样,必须作为一个完整的类型。对其类型做拆分进行泛型化是非法的。

      由于C++0x的Lambda表达式的具体类型不对程序员开放,因此它即不能作为模板形参亦无法作为模板函数的形参,但是它可以在模板函数内使用泛型。

      1. #include <iostream>
      2. using namespace std;
      3.  
      4. template <typename T>
      5. void LambdaTest(T)
      6. {
      7.     T a = 100;
      8.  
      9.     auto ref = [a](const T& b) -> T { return a + b; };
      10.  
      11.     cout << "The value is: " << ref(200) << endl;
      12. }
      13.  
      14.  
      15. int main(void)
      16. {
      17.     LambdaTest((short)0);
      18. }


      上 述代码可以在VS2010β以及Intel C++ Compiler11.0通过编译并正常运行。
      然而比较奇怪的是Blocks在模板函数内的 表现就非常不好——

      1. template <typename T>
      2. void BlockTest(void)
      3. {
      4.     void (^pBlocks)(void) = ^{};
      5. }
      6.  
      7. int main(int argc, const char* argv[])
      8. {
      9.     BlockTest<void>();
      10. }


      上 面这段代码中,模板函数BlockTest中pBlocks根本就没用泛型,也无法通过编译,而且报的错误是internal error: segmentation fault。而只有下面这种情况才能通过编译,但实际上是没有任何意义的:

      1. #import <Foundation/Foundation.h>
      2.  
      3. template <typename T>
      4. void BlockTest(void)
      5. {
      6.     void (^pBlock)(void) = nil;
      7. }
      8.  
      9. int main(int argc, const char* argv[])
      10. {
      11.     BlockTest<void>();
      12. }


      可 见,Blocks作为Lambda表达式而言已经是非常棒的,但与lambda函数功能相比就稍逊一些,尤其是与泛型结合时,表现得很一般。其实这也是与 Blocks的实现有关的。Blocks仍然像objective-C的很多特性那样,主要是靠动态实现的,在编译时所花的精力较少。因此它目前也无法用 于iPhone开发,因为还需要有专门的运行时库。
      我想,等到C++0x正式出台后,C++0x中的lambda表达式和lambda函数可能会 用得更多些。然而,能够在C以及objective-C中用上Lambda特性也算是一种福份了,呵呵。