C++模块介绍第一部分:主模块接口

在这个系列文章中,我们将会探索如何从命令行中构建C++模块和头文件单元,以及如何使用/引用C++模块。
今天的这篇文章的主要目的是介绍如何构建一个C++模块,并从命令行中使用主模块接口,我们还会介绍需要使用到的一些编译开关。
请注意:这个系列教程会主要关注在如何处理IFC和对象文件输出,我们不会讨论其他类型的编译器输出例如PDB文件。

和C++模块相关的编译器开关

C++模块介绍第一部分:主模块接口

C++模块介绍第一部分:主模块接口

构建一个C++模块接口

从这一章节开始,我们将假定你已经搭建好了一个命令行编译器环境,并且已经创建了一个包含测试文件的文件夹。
让我们先来看看下面的基础场景:

C++模块介绍第一部分:主模块接口

编译上述代码很简单,直接使用如下的命令行即可:

C++模块介绍第一部分:主模块接口

下面我们介绍下这个m.ixx文件,.ixx文件后缀是MSVC编译器中默认的模块接口。如果你希望使用自定义的扩展后缀,则你必须使用/interface和/TP这两个编译开关来编译。下面我们举个例子,如果你的模块文件名为my-module.cppm,则你可以使用下图中的编译指令进行编译:

C++模块介绍第一部分:主模块接口

在第一行中,我们编译了模块接口,在底层,会发生下列两件事情:
1. 编译器会基于输入文件名来创建一个结果对象文件(Resulting object file),在这个例子中,我们会基于m.ixx来创建一个m.obj。
2. 编译器接着会基于模块接口名称创建一个结果IFC文件(Resulting IFC file),在这个例子中,这个结果IFC文件为MyModule.ifc。请注意,输入文件的名称不会影响结果IFC文件的名称,例如,如果你的模块文件名为foobar.ixx,则生成的IFC文件还是为MyModule.ifc。

如果我们去掉上面的两个隐含点,我们最终会得到一个如下所示的命令行:

C++模块介绍第一部分:主模块接口

在模块导入端,我们可以使用编译器隐含的查找功能来找到我们构建的模块:

C++模块介绍第一部分:主模块接口

发生了什么?这么神奇?
基操勿六,在MSVC编译器中,我们实现了一个十分默契的模块查找算法。因为编译器基于模块接口名称生成了模块接口IFC,所以它可以轻松地知道这个IFC文件所在的位置。在上面的应用场景中,我们在客户中尝试导入一个名为MyModule的模块接口,所以在磁盘上一定会存在一个MyModule.ifc,的确,这个文件实际是存在的。
请注意,这个隐式查找算法会查找当前文件夹以及在/ifcSearchDir中指定的其他所有文件夹。

让我们考虑这样一种使用场景:结果IFC文件不在临时文件夹中,下图展示了这样一种文件夹结构:

C++模块介绍第一部分:主模块接口

让我们再假定编译器的命令行的根目录为./,同时我们希望所有的输出都发送至bin\文件夹。下面是一个完整的命令行指令:

C++模块介绍第一部分:主模块接口

这个指令中,有很多细节需要注意的,让我们简化这条指令,仅编译main.cpp,而不进行链接。

C++模块介绍第一部分:主模块接口

请注意,这里的/Fo编译开关告诉了编译器我们将结果对象文件创建到哪个地方。更进一步地,为了确保编译器可以判断目标位置是一个有效的文件夹,请在参数的结尾添加一个反斜杠(\)。

如果我们希望使用编译器隐式的命名机制,那么可以使用下面的命令行指令:

C++模块介绍第一部分:主模块接口

可以看得出来,我们在每个命令行选项后面,都添加了一个参数用来指定一个文件夹位置。

模块接口依赖性

通常,在一个大型工程中,我们不会仅仅构建一个单一的模块接口,而是会包含很多的模块接口,用来描述各个功能模块。在这一章节,我们会探索如果构建一个依赖模块接口的翻译单元(translation unit)。

让我们假定有如下图所示的一个文件夹结构:

C++模块介绍第一部分:主模块接口

我们将代码放到了吉特哈布上。
当你浏览这些代码时,你会发现,许多这些模块/源文件包含对模块接口的引用,并且这些接口可能引用另一个接口。从代码核心层面来看,其基本的依赖图如下所示:

C++模块介绍第一部分:主模块接口

下图中,展示了我们使用的全部命令行指令:

C++模块介绍第一部分:主模块接口

看起来,是挺啰嗦的。你可能会注意到的这样一件事:当我们构建src\shop\shop-unit.cpp时,我们需要对两种类型和shop的引用,即使没有显式导入任何一个接口。这样做的原因是因为util对正确解析Product的类型具有隐式依赖性,并且因为它是模块shop的一个模块单元,隐式导入模块接口shop,此行为由C++标准所定义。

使用上面的技术点,我们可以通过使用隐式命名机制和隐式查找机制来简化命令行指令:

C++模块介绍第一部分:主模块接口

这样看起来好多了。我们可以进一步作出简化:因为编译器cl.exe会以一种线性的方式来处理每一个源文件:

C++模块介绍第一部分:主模块接口

上述指令使用隐式命名/查找机制,并结合了编译器的线性处理行为。
请注意,上述指令在并行编译开关开启(/MP打开)时不会正常工作。

为了最后的完整性,我们可以在一个单一的命令行中使用显式的接口命名,如下图所示:

C++模块介绍第一部分:主模块接口

这些命令行中的任何一个都能正常工作的原因是,编译器不会尝试使用/reference选项做任何特殊的事情,除非使用了指定IFC的名称,并且如果你知道的话,为命令行添加/reference选项不会产生额外的成本,该模块将在输入序列中的某个点生成。

总结

在第二部分中,我们会演示如何处理模块接口分区。在最后的第三部分中,我们会介绍如何处理头文件单元。
不想再忍受传统C++代码的耦合性,请试试C++模块,说不定会打开一个全新的世界。

最后

Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新的开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。
本文来自:《Using C++ Modules in MSVC from the Command Line Part 1: Primary Module Interfaces》

最近我写了个东西

正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。

C++模块介绍第一部分:主模块接口

上一篇:Chrome 浏览器提示adobe flash player不是最新版本


下一篇:Spring Security 解析(五) —— Spring Security Oauth2 开发