求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
为什么应该用模块取代C/C++中的头文件?
 

作者:王然 ,发布于2012-12-11,来源:CSDN

 

为什么应该使用模块(Module)替代头文件(Header)?

头文件糟透了!

众所周知,C程序在编译时一般会预处理头文件:

常规解决办法如下:

  1.LLVM_WHY_PREFIX_UPPER_MACROS 
  2.    LLVM_CLANG_INCLUDE_GUARD_H 
  3.    template<class _Tp> 
  4. 
  5.const _Tp& min(const _Tp &__a, 
  6.               const _Tp &__b); 
  7. 
  8.#include <windows.h> 
  9.#undef min // because #define NOMINMAX 
 10.#undef max // doesn’t 

但结果依然不够理想,比较一下代码与程序大小你会发现:

另外,头文件形式的可扩展性天生不足。假设有n个源文件,每个源文件引用了m个头文件,那么构建过程的开销会是m×n。这在C++中表现得尤为糟糕。所以预说处理头文件是一个非常糟糕的解决方案。

C家族的模块系统

模块是什么?

  • 库的接口(API)
  • 库的实现

使用“import”导入已命名的模块:

import会在源文件中忽略预处理状态,并且选择性导入,所以弹性(resilience)非常好。

使用“import”会导入什么?

  • 函数、变量、类型、模板、宏,等等;
  • 公开API——其它的都隐藏;
  • 没有特别的命名空间机制。

C/C++引入模块会怎么样?

引入模块的目标在于:

  • 在源文件中指定模块名称;
  • API公开;
  • 没有头文件!

要编写一个模块非常简单,只需要使用export:

但是这么做会遇到很多遗留问题:

  • 需要迁移现在基于头文件的类库;
  • 与不支持模块的编译器的互操作性;
  • 工具需要理解模块;

所以应该使用引入模块的过渡方案——直接从头文件中构建模块。这么做有以下好处:

  • 头文件有利于互操作;
  • 程序员不需要完全改变自己习惯的开发模式;

模块地图(Module Map)

模块地图是模块的关键,用来定位模块相关(子)模块,包含以下功能:

  • 模块定义命名(子)模块

  • 头文件在(子)模块中包含命名头文件的内容

保护伞头文件(Umbrella Header)

  • 保护伞头文件会在其目录下包含所有头文件信息
  • 使用通配符submodules (module *) 可以为每一个包含的头文件创建一个子模块:
  1.AST/Decl.h -> ClangAST.Decl 
  2.AST/Expr.h -> ClangAST.Expr 

模块编译过程:

  1. 找到命名模块的module map;
  2. 产生一个独立编译器实例;
  3. 在module map中解析头文件。

编辑模块文件过程:

  1. 在“import”声明处导入模块文件;
  2. 把模块文件保存在缓存中待重用。

从头文件到模块化

从头文件编程转换到使用模块非常简单:

库方面:合并复合定义的结构、函数、宏,并且为头文件导入依赖,最后编写好模块地图;

开发者方面只需要从“#include”过渡到“import”:

  1. 把“#inlude”都换成“import”;
  2. 使用module maps确定(子)模块(类似头文件里的“#include”);

当然,你也可以使用工具来自动化重写代码,非常简单。

工具

编辑性能

使用模块能够提升语法解析性能:

  • 模块化的头文件只需要解析一次,之后放在缓存中,于是m×n --> m+n
  • 所有基于源(source-based)工具都能带来好处
  • 自动链接大大简化了库的使用
  • 自动导入可以阻止“#include”带来的可怕的调试结果


调试流

通过DWARF的双程调试有损耗:

  • 只能获得“用过”类型和函数
  • 丢失了行内函数、模板

另外调试过程还会出现信息冗余

那使用模块的调试又会怎样?

1.提高了构建性能

  • 编译器发出的DWARF更少
  • 链接器清除重复的DWARF也更少

2.提高了调试体验

  • 调试器的ASF精度非常完美
  • 调试器不需要寻找DWARF

总结

总而言之,C/C++使用模块化非常有潜力:

  • 编译/构建时间的缩短
  • 修复各种预处理问题
  • 更好的工具体验
  • 通过设计,能够平稳地过渡
  • Clang实现已经在进行了

这个Slide在Hacker News上引起了激烈讨论,大部分网友还是赞成模块化的方式:

baberman:对我来说没什么不便,而且还给出了过渡方案,可能会很适合某些C/C++项目。我们应该对任何提升C/C++性能的想法持开放态度。

greggman:预处理有什么不好吗?如果我不想用预处理,我完全可以使用Objective-C等。现在的机器性能已经够强大了,import在编译上的性能优势对我来说没有任何吸引力,我更喜欢C/C++的传统方式。

msbarnett反驳greggman:我认为这正是这份提议的精髓所在,你既可以保留使用#include,也可以从现在开始就转向import模块的方式。

_djo_:这个想法会非常有前途!要说缺点的话就是来得太迟了!

nkurz:我不同意“m个头文件+n个源文件 --> m×n倍编译性能”。只要使用“#ifndef _HEADER_H”就不会出现这个问题,或者,为什么不使用#include_once <header.h>来解决呢?预处理很可怕吗?预处理确实有一些问题,但是却是可以克服的。

SeoxyS:我不关心性能,现在性能已经不是问题了,我更关心怎样给开发者更少的负担。


   

Visual C++编程命名规则
任何时候都适用的20个C++技巧
C语言进阶
串口驱动分析
轻轻松松从C一路走到C++
C++编程思想
更多...   


C++并发处理+单元测试
C++程序开发
C++高级编程
C/C++开发
C++设计模式
C/C++单元测试


北京 嵌入式C高质量编程
中国航空 嵌入式C高质量编程
华为 C++高级编程
北京 C++高级编程
丹佛斯 C++高级编程
北大方正 C语言单元测试
罗克韦尔 C++单元测试
更多...