四大特性模块(module)

四大特性模块(module)module 的动机 C 20 中新增了四大特性之一的模块 module 用以解决传统的头文件在编译时间及程序组织上的问题 modules 试图解决的痛点 能最大的痛点就是编译慢 头文件的重复替换 比如你有多个翻译单元 每一个都调用了 iostream 就得都处理一遍 预处理完的源文件就会立刻膨胀 真的会很慢 有了

大家好,我是讯享网,很高兴认识大家。

module的动机

C++20中新增了四大特性之一的模块(module),用以解决传统的头文件在编译时间及程序组织上的问题。

modules 试图解决的痛点

能最大的痛点就是编译慢, 头文件的重复替换, 比如你有多个翻译单元, 每一个都调用了 iostream, 就得都处理一遍. 预处理完的源文件就会立刻膨胀. 真的会很慢.

有了 modules 以后, 我们可以模块化的处理. 已经编译好的 modules 直接变成编译器的中间表示进行保存, 需要什么就取出什么, 这就非常地快速了.

比如你只是用了 cout 的函数, 那么编译器下次就不需要处理几万行, 直接找 cout 相关函数用就行了. 太快了, 太快了! Google 使用 modules 的经验表明, 编译速度飞快.

除此之外, 封装什么的也可以做得很好, 可以控制 modules 的哪些部分可以暴露于外界. 这些也是非常不错的地方.

可以看倒数第二节, C++ 之父介绍了 modules 的发展进程.

 

modules 的痛点

modules 目前没有构建系统.

modules 的构建必须是按照依赖关系进行构建的. 也就是说子模块必须提前构建, 这就对传统的并行构建系统提出了挑战. 手工写编译命令是权宜之计, 想用上像 cmake 这样的构建工具, 得等到未来才有了.

#include头文件有下面这些负面影响:

  • 低效:头文件的本职工作是提供前置声明,而提供前置声明的方式采用了文本拷贝,文本拷贝过程不带有语法分析,会一股脑将需要的、不需要的声明全部拷贝到源文件中。
  • 传递性:最底层的头文件中宏、变量等实体的可见性,可以通过中间头文件“透传”给最上层的头文件,这种透传会带来很多麻烦。
  • 降低编译速度:加入 a.h 被三个模块包含,则 a 会被展开三次、编译三次。
  • 顺序相关:程序的行为受头文件的包含顺影响,也受是否包含某一个头文件影响,在 C++ 中尤为严重(重载)。
  • 不确定性:同一个头文件在不同的源文件中可能表现出不同的行为,导致这些不同的原因,可能源自源文件(比如该源文件包含的其他头文件、该源文件中定义的宏等),也可能源自编译选项。

module模块机制优势:

  • 无需重复编译:一个模块的所有接口文件、实现文件,作为一个翻译单元,一次编译后生成 pcm,之后遇到 Import 该模块的代码,编译器会从 pcm 中寻找函数声明等信息,该特性会极大加快 C++ 代码的编译速度。
  • 隔离性更好:模块内 Import 的内容,不会泄漏到模块外部,除非显式使用 export Import 声明。
  • 顺序无关:Import 多个模块,无需关心这些模块间的顺序。
  • 减少冗余与不一致:小的模块可以直接在单个 cppm 文件中完成实体的导出、定义,但大的模块依然会把声明、实现拆分到不同文件。
  • 子模块、Module Partition 等机制让大模块、超大模块的组织方式更加灵活。
  • 全局模块段、Module Map 制使得 Module 与老旧的头文件交互成为可能。

为什么会变快?

modules 编译出来会有两个部分:

编译器缓存, 基本上代表了全部源代码信息. 编译器已经处理好了源代码, 并且把内部缓存留了下来. 下次读取的时候会更快. 主流三大编译器都有 lazy loading 的功能, 可以只取出需要的部分. 这不就快了?

编译出来的 object, 这个 object 只有链接的时候需要,而且不需要特别处理, 也不需要再次编译.

所以啊, 就是有了这个缓存, 才会让它更快. 各个编译器不能通用哦. 而且编译选项不同的时候, 内部的表示不一样, 所以也不能通用.

这个缓存文件:

在 Clang 那里, 叫 BMI, 后缀名是 .pcm.

在 GCC 那里, 叫 CMI, 后缀名是 .gcm.

在 MSVC 那里, 叫 IFC, 后缀名是 .ifc.

对于这个缓存文件, 这三家编译器还使用了 lazy loading 的技术. 也就是说需要哪些定义/声明就加载哪些. 编译器的这种 lazy loading 进一步提升了速度.

模块单元(Module Unit)

C++中的基本编译单元称为“翻译单元(即translation unit)”。一个翻译单元由一个源文件和该源文件所直接或间接包括的头文件的内容组成。C++20中的模块是一种新的翻译单元,称为模块单元。


讯享网

为了支持module, c++20 引入了三个关键字export/import/module。

module关键字:module用于声明一个模块,其前方也可以带上export。

export关键字:export用于声明一个module名和标记内容的导出性。

import关键字:import用于导入一个module。

模块声明

翻译单元可以有一个模块声明,这种情况下它们会被视为模块单元模块声明在有提供时必须是翻译单元的首个声明(后面提到的全局模块片段除外)。每个模块单元都对应一个模块名(可以带一个分区),它在模块声明中提供。

模块名包含由点分隔的一个或多个标识符(例如:mymodulemymodule.mysubmodulemymodule2...)。点没有内在含义,不过它们会非正式地用于表示继承关系。

一个具名模块是一组模块名相同的模块单元。

声明中带有关键词 export 的模块单元是模块接口单元。其他模块单元被称为模块实现单元

对于每个具名模块,必须有恰好一个未指定模块分区的模块接口单元。这个模块单元被称为主模块接口单元。在导入对应的具名模块时可以使用它导出的内容。

// (每行表示一个单独的翻译单元) 
export module A
;// 为具名模块 'A' 声明主模块接口单元

module A;// 为具名模块 'A' 声明一个模块实现单元

module A;// 为具名模块 'A' 声明另一个模块实现单元,比如:一个模块接口单元对应多个模块实现单元时

export module A.B;// 为具名模块 'A.B' 声明主模块接口单元

module A.B;// 为具名模块 'A.B' 声明一个模块实现单元

导出声明和定义

模块接口单元可以导出声明和定义,这些内容可以导入到其他翻译单元。它们可以有 export 关键词作为前缀,或者处于 export 块中。

使用export关键字从模块中导出实体(如,类、函数、常量、其他模块等)。任何没有从模块导出的内容只在该模块可见。所有导出实体的集合称为模块接口。

被导出的C++实体需在第一次声明时用export关键字,其后的声明或定义均不需再指定export。

export 声明

export {  声明序列(可选) }

export module 模块名;

小讯
上一篇 2025-02-14 14:20
下一篇 2025-01-05 21:51

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/53650.html