C++ 的模块标准是否解决了对调用者隐藏私有数据的问题?
Does the module standard for C++ solve the problem of hiding private data from callers?
在 C++ 中,模块正在标准化以解决 #include 膨胀等问题。 C++ 中的编译器必须解析太多内容。
而且,由于 C++ 高效地内联存储数据,即使调用者也必须了解对象的内存布局。
即将发布的模块标准是否解决了这个问题?
示例:
class GLWin {
private:
GLFWwindow* win;
glm::mat4 projection;
...
};
包含指向内部实现的指针的对象可以通过空声明来解耦,即:
class GLFWwindow;
但是如果为了性能我们在 window 中包含 mat4 对象,那么我们需要知道大小,目前这意味着包含一个定义,引入一个头文件,由于级联,这个头文件通常很大包括。模块中是否有任何机制可以隐藏细节并允许为对象保留正确数量的 space 同时使其像指针一样不透明?
模块概念改变了我们对依赖关系的看法。将不再有 headers,只有二进制模块接口 (BMI),它由编译器生成并包含有关 object 大小、object 结构和依赖项的所有信息。您的 class 模块必须依赖于 GLFWindow 和 glm::mat 的模块,否则您无法编译它。因此,从某种意义上说,您仍然必须将内部数据公开给其他 classes,但您的编译器不必遍历所有包含内容,而只需遍历 BMI 的导入,这是理解 [=14] 所必需的=] 接口,如果它发现多次与依赖项相同的 BMI,它只会解析一次。
这也意味着,您将不再在单独的文件中分隔定义和声明,因为这没有任何意义。你最终会得到一个看起来更像 Java .class 文件的东西。
模块无法实现这样的系统,即模块外部的代码不知道类型的私有成员是什么。这不适用于静态反射提议,它允许对类型的私有成员进行查询和迭代。
模块所做的是:
当您获得这些类型的递归 "inclusions" 时,它们实际上不会 将这些内部结构公开 给外部代码。在您的示例中,假设 glm::mat4
来自名为 GLM
的模块。声明 GLFWin
的模块将具有 import GLM
,因为它需要这些定义才能工作。但是,这是一个实现细节,因此您不会执行 export import GLM
.
现在,其他人来导入您的模块。要执行该导入,编译器将必须读取 GLM
模块。但是因为你的模块不导出GLM
,导入你的模块的代码不能使用它。也就是说,他们自己不会使用 glm::mat4
或其他任何东西,除非他们自己导入该模块。
这似乎没什么区别,因为 GLM
模块仍然是必需的,但它很重要。用户不会仅仅因为该模块正被他们正在使用的模块使用而从该模块获得接口。
这些进口几乎没有那么痛苦。编译模块的结果应该是一个文件(通常称为 BMI,“二进制模块接口”),这是编译器可以快速读取并转换为其内部数据结构的文件。此外,如果您在同一个文件中编译多个翻译单元编译器进程,然后它们可以共享加载的模块。毕竟,GLM
不会根据你从哪里导入它而改变,所以没有理由甚至重新加载模块;你只需使用内存中已有的东西。
最后是重新编译。如果您使用 headers,并且更改了 GLM headers,那么包含它们的每个文件都需要重新编译。模块仍然如此,但痛苦要少得多。
让我们假设您的 GLFWin
创建模块和使用它的模块在某个时候都使用 std::vector
。现在,假设您更改了 GLM,因此您必须重新编译这两个模块。在 header 世界中,这个 also 意味着两个文件都必须重新编译 <vector>
header,即使它没有改变也没有完全依赖于 GLM。这就是文本包含的工作原理。
在模块化世界中,他们不必重新编译 vector
模块。它不以任何方式依赖于 GLM
模块,因此它可以只使用已经存在的 vector
模块。对于不依赖于 GLM
的 any 包含模块也是如此。因此,虽然您仍然需要一系列重新编译,但重新编译本身应该快得多,因为不必重新编译每个翻译单元本身使用的所有内容。一个 5000 行的文件重新编译就像一个 5000 行的文件,而不是 5000 + 不管它包含多少行。
在 C++ 中,模块正在标准化以解决 #include 膨胀等问题。 C++ 中的编译器必须解析太多内容。
而且,由于 C++ 高效地内联存储数据,即使调用者也必须了解对象的内存布局。
即将发布的模块标准是否解决了这个问题?
示例:
class GLWin {
private:
GLFWwindow* win;
glm::mat4 projection;
...
};
包含指向内部实现的指针的对象可以通过空声明来解耦,即:
class GLFWwindow;
但是如果为了性能我们在 window 中包含 mat4 对象,那么我们需要知道大小,目前这意味着包含一个定义,引入一个头文件,由于级联,这个头文件通常很大包括。模块中是否有任何机制可以隐藏细节并允许为对象保留正确数量的 space 同时使其像指针一样不透明?
模块概念改变了我们对依赖关系的看法。将不再有 headers,只有二进制模块接口 (BMI),它由编译器生成并包含有关 object 大小、object 结构和依赖项的所有信息。您的 class 模块必须依赖于 GLFWindow 和 glm::mat 的模块,否则您无法编译它。因此,从某种意义上说,您仍然必须将内部数据公开给其他 classes,但您的编译器不必遍历所有包含内容,而只需遍历 BMI 的导入,这是理解 [=14] 所必需的=] 接口,如果它发现多次与依赖项相同的 BMI,它只会解析一次。
这也意味着,您将不再在单独的文件中分隔定义和声明,因为这没有任何意义。你最终会得到一个看起来更像 Java .class 文件的东西。
模块无法实现这样的系统,即模块外部的代码不知道类型的私有成员是什么。这不适用于静态反射提议,它允许对类型的私有成员进行查询和迭代。
模块所做的是:
当您获得这些类型的递归 "inclusions" 时,它们实际上不会 将这些内部结构公开 给外部代码。在您的示例中,假设
glm::mat4
来自名为GLM
的模块。声明GLFWin
的模块将具有import GLM
,因为它需要这些定义才能工作。但是,这是一个实现细节,因此您不会执行export import GLM
.现在,其他人来导入您的模块。要执行该导入,编译器将必须读取
GLM
模块。但是因为你的模块不导出GLM
,导入你的模块的代码不能使用它。也就是说,他们自己不会使用glm::mat4
或其他任何东西,除非他们自己导入该模块。这似乎没什么区别,因为
GLM
模块仍然是必需的,但它很重要。用户不会仅仅因为该模块正被他们正在使用的模块使用而从该模块获得接口。这些进口几乎没有那么痛苦。编译模块的结果应该是一个文件(通常称为 BMI,“二进制模块接口”),这是编译器可以快速读取并转换为其内部数据结构的文件。此外,如果您在同一个文件中编译多个翻译单元编译器进程,然后它们可以共享加载的模块。毕竟,
GLM
不会根据你从哪里导入它而改变,所以没有理由甚至重新加载模块;你只需使用内存中已有的东西。最后是重新编译。如果您使用 headers,并且更改了 GLM headers,那么包含它们的每个文件都需要重新编译。模块仍然如此,但痛苦要少得多。
让我们假设您的
GLFWin
创建模块和使用它的模块在某个时候都使用std::vector
。现在,假设您更改了 GLM,因此您必须重新编译这两个模块。在 header 世界中,这个 also 意味着两个文件都必须重新编译<vector>
header,即使它没有改变也没有完全依赖于 GLM。这就是文本包含的工作原理。在模块化世界中,他们不必重新编译
vector
模块。它不以任何方式依赖于GLM
模块,因此它可以只使用已经存在的vector
模块。对于不依赖于GLM
的 any 包含模块也是如此。因此,虽然您仍然需要一系列重新编译,但重新编译本身应该快得多,因为不必重新编译每个翻译单元本身使用的所有内容。一个 5000 行的文件重新编译就像一个 5000 行的文件,而不是 5000 + 不管它包含多少行。