模块单元分类的这两个轴是正交的,从某种意义上说,模块可以独立地成为这些分类的任何组合的一部分。但是,该标准为 4 种分类中的每一种定义了许多专门的规则,这使得每种分类的使用不仅仅是分类定义所表明的。
在查看这些组合之前,我们首先需要定义要分类的内容。
接口单元与实现单元
接口单元不是您可以导入的东西。嗯,你能够,但那不是定义“接口单元”。模块单元是模块M 的接口单元,因为它是界面模块M。这意味着,如果有人导入模块M,构建系统将需要构建模块M 的所有接口单元。在有人可以导入M 之前,不需要构建模块M 的实现单元。
这是全部接口/实现分类意味着(虽然它不是全部做,但我们会解决的)。实现单元在概念上是模块M 的一部分,但它们不是其接口的一部分。
重要的是要注意成为“模块M 的一部分”意味着什么。如果在M 的权限内声明了一个实体,那么它就是M 的一部分。所以如果你想再次声明它(因为你正在定义它,比如说),第二个声明必须还在M ([basic.link]/10) 的范围内。
这是所有类型的实施单位的重点:在M 的范围内而不对其做出贡献界面.
分区与纯
对于不是分区的模块单元,标准中没有术语,因此我将此类模块单元称为“纯”。
作为模块M 的分区X 的模块单元可以通过分区导入语法导入:import :X。这只能由作为M 一部分的模块单元完成。纯模块单元不能以这种方式导入。
所以分区与纯分类是关于模块内的模块单元是否可以通过特殊语法导入同一模块内的某些模块单元。
同样重要的是要注意导入某些东西的含义。导入事物是在翻译单元的基础上完成的。导入非分区模块意味着导入该模块的所有接口模块单元TU。导入一个模块分区就是只导入那个分区单元。
但是,export只重要对于通过代码导入的声明外部声明它们的模块。因此,如果M 的某个模块单元导入M 的分区单元,它将看到该分区单元权限内的所有声明,无论它们是否为exported ([basic.scope.namespace]/2)。
现在,让我们检查 C++ 为四种组合中的每一种定义的所有特殊情况规则。到白衣:
纯接口单元
这种组合附加了许多特殊规则,以至于标准给它起了一个名字:主接口单元对于模块M。
如果我们只看上面的规则,M 的一个主要接口单元是M 接口的一个组件。并且由于它是纯的,M 的主接口单元不能通过分区语法导入。
但随后标准在此之上设置了更多规则:
-
对于任何模块M,应有准确且只有一个M ([module.unit]/2) 的主要接口单元。
-
全部分割M的接口单元必须由M ([module.unit]/3) 的主接口单元export imported(直接或间接)。
-
如果没有M 的其他实现或接口单元,则该文件可能有一个私有模块片段,用于将M 的未导出内容放在单个文件中([module.private.frag])。
简而言之:如果构建系统需要构建模块M,那真正意味着它需要构建这个文件(以及它进口的任何东西)。该文件是定义import M; 将公开的内容的导入根。
接口分区单元
这样的模块单元是模块M的接口的一个组成部分,因此必须编译生成模块M。但这被处理了,因为主接口单元必须包括所有这些。它们也可以包含在内……我们知道,因为主要接口单元必须包含它们。
因此,对于这个没有任何其他地方未涵盖的特殊规则。
接口分区单元的意义只是将大模块接口分成多个文件的工具。
纯执行单元
作为实现单元,它们对模块的接口没有贡献。并且作为纯模块单元,它们不能作为分区导入。这意味着发生在他们身上的一切停留在其中(就导入任何东西而言)。
但他们也有一些特殊的规则:
-
M的所有纯实现单元含蓄地import M; ([module.unit]/8)。
-
他们不能明确的import M; ([module.import]/9)。
如果一个实现单元的目的是能够定义一个模块的接口特性,那么这些规则是有意义的。如前所述,只有M 的模块单元可以定义作为M 接口一部分的声明。所以这些是大多数定义所在的文件。
因此,他们也可以隐式包含M 的接口以方便使用。
分区执行单元
这是一个模块单元,它不是其模块接口的一部分。但是由于是分区,所以可以被M的其他模块单元导入。
这个声音矛盾的,直到你得到这个特殊情况规则:
- 模块单元不能
export import 分区实现单元 ([module.import]/8)。
因此,即使接口单元导入了实现分区,它也无法导出该分区。实施单元也无法导出其中定义的任何内容(您不能将未导出的内容重新声明为exported 稍后)。
但请记住export只重要用于导入非分区(即:其他模块)。由于分区只能由它们自己的模块的成员导入,并且导入的分区中的所有声明都将可供导入代码使用,所以我们所拥有的是模块单元,其中包含模块实现私有的声明,但需要可被多个模块实现单元访问。
这一点特别重要,因为模块名称是全球的, 而分区名称是模块的本地名称。通过将内部共享代码放入实现分区,您不会用模块的实现细节污染模块名称的全局空间。