您的位置:首页 > 编程语言 > C语言/C++

为什么应该用模块取代C/C++中的头文件?

2013-01-12 10:48 274 查看
摘要:本文整理自Apple C++工程师Doug Gregor的演讲Slide,他表示希望使用模块(Module)这一概念替代C/C++中的头文件,现已被C++标准化委员会任命为Module研究组的主席,研究该提议的可能性。考虑到Apple的开源项目LLVM在编辑器领域中的地位,这一提议非常值得重视。

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

头文件糟透了!

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



常规解决办法如下:

LLVM_WHY_PREFIX_UPPER_MACROS 
    LLVM_CLANG_INCLUDE_GUARD_H 
    template<class _Tp> 
 
const _Tp& min(const _Tp &__a, 
               const _Tp &__b); 
 
#include <windows.h> 
#undef min // because #define NOMINMAX 
#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 *) 可以为每一个包含的头文件创建一个子模块:
AST/Decl.h -> ClangAST.Decl 
AST/Expr.h -> ClangAST.Expr 

模块编译过程:

找到命名模块的module map;
产生一个独立编译器实例;
在module map中解析头文件。
编辑模块文件过程:

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

从头文件到模块化

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

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

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

把“#inlude”都换成“import”;
使用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:我不关心性能,现在性能已经不是问题了,我更关心怎样给开发者更少的负担。

各位CSDN网友是怎样看的呢?欢迎一起讨论。

相关链接:Doug的SlideHacker News上的讨论
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: