3

在 C++ 中,模块正在被标准化以解决 #include 膨胀等问题。C++ 中的编译器必须解析太多。

而且,由于 C++ 以高效的方式内联存储数据,因此即使调用者也必须了解对象的内存布局。

即将推出的模块标准是否解决了这个问题?

例子:

class GLWin {
private:
  GLFWwindow* win;
  glm::mat4 projection;
  ...
};

包含指向内部实现的指针的对象可以通过空声明来解耦,即:

类 GLFW 窗口;

但是,如果为了性能,我们在窗口中包含 mat4 对象,那么我们需要知道大小,这目前意味着包含一个定义,引入一个由于级联包含而通常很大的头文件。模块中是否有任何机制可以隐藏细节并允许为对象保留正确的空间量,同时使其像指针一样不透明?

4

2 回答 2

8

模块无法实现系统,使得模块外部的代码不知道类型的私有成员是什么。这不适用于静态反射提议,它允许对类型的私有成员进行查询和迭代。

模块所做的就是使它:

  1. 当您获得这些递归“包含”时,它们实际上并没有将这些内部暴露给外部代码。在您的示例中,假设它glm::mat4来自一个名为GLM. 声明的模块GLFWin将具有import GLM,因为它需要这些定义才能工作。但是,这是一个实现细节,所以你不会做export import GLM.

    现在,其他人出现并导入您的模块。要执行该导入,编译器必须读取GLM模块。但是因为你的模块没有导出GLM,导入你的模块的代码不能使用它glm::mat4也就是说,除非他们自己导入该模块,否则他们自己不会使用或其他任何东西。

    这似乎没有太大区别,因为GLM仍然需要该模块,但它是一个重要的模块。用户不会仅仅因为他们正在使用的模块正在使用该模块而从模块中获得接口。

  2. 这些进口几乎没有那么痛苦。编译模块的结果应该是一个文件(通常称为 BMI,“二进制模块接口),这是编译器可以快速读取并转换为其内部数据结构的文件。此外,如果您在同一个中编译多个翻译单元编译器进程,然后它们可以共享加载的模块。毕竟,GLM不会根据您从哪里导入它而改变,因此甚至没有理由重新加载模块;您只需使用内存中已经存在的内容。

    最后是重新编译。如果您正在使用头文件,并且您更改了 GLM 头文件,那么包含它们的每个文件都需要重新编译。这仍然适用于模块,但以一种不那么痛苦的方式。

    让我们假设您的GLFWin-creating 模块和使用它的模块都std::vector在某个时候使用。现在,假设您更改了 GLM,因此您必须重新编译这两个模块。在头文件世界中,这意味着两个文件都必须重新编译<vector>头文件,即使它没有改变并且根本不依赖于 GLM。这就是文本包含的工作原理。

    在模块化世界中,他们不必重新编译vector模块。它不GLM以任何方式依赖于模块,因此它可以只使用已经存在的vector模块。对于任何不依赖于GLM. 因此,尽管您仍然需要级联的重新编译,但重新编译本身应该更快,因为不必重新编译每个翻译单元本身使用的所有内容。5000 行文件像 5000 行文件一样重新编译,而不是 5000 +,无论它包含多少行。

于 2019-03-11T15:12:32.237 回答
0

模块概念改变了我们对依赖关系的看法。将不再有头文件,而是二进制模块接口 (BMI),它由编译器生成并包含有关对象大小、对象结构和依赖关系的所有信息。你的类的模块必须依赖于 GLFWindow 和 glm::mat 的模块,否则你不能编译它。因此,从某种意义上说,您仍然必须将内部数据公开给其他类,但是您的编译器不必爬取所有包含,而只需爬取 BMI 的导入,这是理解类/函数接口以及它是否发现所必需的多次相同的 BMI 作为依赖,它只会解析一次。

这也意味着,您将不再在单独的文件中分离定义和声明,因为它没有任何意义。您最终会得到一个看起来更像 Java .class 文件的东西。

于 2019-03-11T10:40:14.983 回答