我一直在阅读 C++ 模块提案(最新草案),但我不完全理解它旨在解决什么问题。
其目的是允许由一个编译器构建的模块供任何其他编译器使用(当然,在相同的操作系统/架构上)?也就是说,该提案是否等同于标准化 C++ ABI?
如果没有,是否正在考虑另一个提案来标准化 C++ ABI 并允许编译器互操作?
预编译头文件 (PCH) 是某些编译器可以为 .cpp 文件生成的特殊文件。它们的本质是:预编译的源代码。它们是通过编译器输入并构建为依赖于编译器格式的源代码。
PCH 通常用于加速编译。您将常用的标头放在 PCH 中,然后只包含 PCH。当您#include
在 PCH 上执行 a 时,您的编译器实际上并没有执行通常的 #include 工作。而是将这些预编译符号直接加载到编译器中。没有运行 C++ 预处理器。没有运行 C++ 编译器。不#包括一百万个不同的文件。加载了一个文件,并且符号直接在编译器的工作区中完全形成。
我提到所有这些是因为模块是完美形式的 PCH。PCH 基本上是建立在不允许实际模块的系统之上的巨大黑客。模块的目的最终是能够获取一个文件,生成一个包含符号的编译器特定的模块文件,然后其他一些文件根据需要加载该模块。这些符号是预编译的,所以再一次,不需要#include 一堆东西,运行编译器等。你的代码说,,import thing.foo
它会出现。
查看任何 STL 派生的标准库头文件。举个<map>
例子。很有可能这个文件要么很大,要么有很多其他文件的#inclusions,从而使生成的文件变得很大。这是必须发生的大量 C++ 解析。它必须发生在其中的每个.cpp 文件#include <map>
中。每次编译源文件时,编译器都必须重新编译相同的东西。超过。结束了。再说一遍。
<map>
编译之间有变化吗?不,但是您的编译器不知道这一点。所以它必须不断地重新编译它。每次接触 .cpp 文件时,它都必须编译该 .cpp 文件包含的每个标头。即使您没有接触那些影响这些标头的标头或源文件。
PCH 文件是解决此问题的一种方法。但它们是有限的,因为它们只是一个黑客。每个 .cpp 文件只能包含一个,因为它必须是 .cpp 文件包含的第一个内容。由于只有一个 PCH,如果你做了一些改变 PCH 的事情(比如添加一个新的头文件),你必须重新编译那个 PCH 中的所有内容。
模块基本上与交叉编译器 ABI 无关(尽管拥有其中一个会很好,并且模块会使定义一个更容易一些)。它们的基本目的是加快编译时间。
模块是 Java、C# 和许多其他现代语言提供的。它们极大地减少了编译时间,仅仅是因为今天头文件中的代码不必每次被包含在内时一遍又一遍地解析。当您说#include <vector>
时, 的内容<vector>
将被复制到当前文件中。#include
真的只是复制和粘贴。
在模块世界中,您只需说import std.vector;
例如,编译器就会加载该模块的查询/符号表。模块文件的格式便于编译器解析和使用。在编译模块时,它也只解析一次。之后,只在编译器生成的模块文件中查询所需的信息。
因为模块文件是编译器生成的,它们将与编译器的 C++ 代码 (AST) 的内部表示非常紧密地联系在一起,因此很可能不可移植(就像今天的.o
//文件一样,因为名称修改等。 )。.so
.a
C++ 中的模块必须比今天的解决方案更好,也就是说,当一个库由一个 *.so 文件和带有 API 的 *.h 文件组成时。他们必须用#includes 解决今天存在的问题,即:
尽管 Xeo 说了什么,Java 或 C# 中不存在模块。事实上,在这些语言中,“加载模块”依赖于“好的,这里有 CLASSPATH 并搜索它以找到任何模块可能提供源文件实际使用的符号”。Java 中的“import”声明根本不是“模块请求”——与 C++ 中的“using”相同(Java 中的“import ns.ns2.*”与 C++ 中的“using namespace ns::ns2”相同) . 我不认为这样的解决方案可以在 C++ 中使用。我能想象的最接近的近似值是 Vala 中的包或 Tcl 中的模块(来自 8.5 版本的那些)。
我想 C++ 模块不可能是跨平台的,也不可能动态加载(需要专用的 C++ 动态模块加载器——这并非不可能,但今天很难定义)。它们肯定会依赖于平台,并且在请求时也应该是可配置的。但是一个稳定的 C++ ABI 实际上只需要在一个系统的范围内,就像现在的 C++ ABI 一样。