【问题标题】:Differences between C++20 module kinds/sizes?C++20 模块种类/大小之间的区别?
【发布时间】:2021-11-02 19:58:32
【问题描述】:

在 C++20 中,对于每个模块 M,必须只有一个源文件开始:

export module M;

(这称为主模块接口单元。)

可选地,每个模块M 可能有额外的源文件:

(1) 零个或多个源文件开始:

module M;

(这些被称为模块实现单元。)

(2) 零个或多个源文件开始(对于一些独特的P):

module M : P;

(这些都是模块实现单元模块分区

(3) 零个或多个源文件开始(对于一些独特的P):

export module M : P;

(这些都是模块接口单元模块分区

因此,在组织代码库以使用模块时,需要做出以下决定:

A.代码库分为多少个模块?

B.对于每个模块,该模块分为多少个源文件?

C.如果 B 的答案不止一个,那么这三种(1,2 或 3)中的哪一种是多余的文件?

在 A 中使用少量模块(粗粒度)和大量模块(细粒度)之间的权衡是什么?对性能有任何技术影响吗?功能差异呢? (有人建议“模块在大块中效果最好”?为什么?)

你什么时候只回答 B 的一个?当对 B 回答多个问题时,C 是如何做出决定的?即 1、2 和 3 之间的功能差异是什么?你什么时候会用一个来代替另一个?

【问题讨论】:

  • 有很多问题,很难回答。但我想知道。当 IDE 对它们的支持足够时,我希望在我们公司的代码库中使用模块(目前:在 Visual Studio 上通常可以,但在 Xcode 上不行)。我希望在使用模块之前先对它们进行一些实验。
  • @prapin Visual Studio 有一个用于管理模块相互依赖的内部工具,但 Xcode 可能还没有。我正在开发这样一个用于 premake 的工具(它具有对 Visual Studio 的早期模块支持)来支持带有 Makefile 的 GCC。如果你好奇this 是存储库。任何反馈表示赞赏。

标签: c++ module c++20 c++-modules


【解决方案1】:

模块实现和 ODR

一个模块可以有多个模块实现单元。它们的解释类似于模块分区的解释。示例:

// mymodule_interface.cpp
export module mymodule;

export int get_int();

[...]
// mymodule_implementation.cpp
module mymodule;   // implicitly imports the module interface!

int get_int() { return 42; }

[...]

这将类似于我们习惯的标头/实现之间的分区。 可能,可以将标头转换为模块实现单元并保持现有源文件不变,在顶部添加export module 命令。但是,我认为 [个人意见] 代码看起来越干净,我们需要浏览的文件越少,并且标头/实现之间的区别导致更多的混乱而不是模块化方法的好处。例如,来自 C# 的这段代码可能是最容易阅读和维护的:

export module mymodule

export int get_int() { return 42; }

export class myclass {
public:
    myclass(int myint) : mMyInt(myint) {}
    int getInt() { return mMyInt; }

private:
    const int mMyInt;
};

这 [当然值得讨论!] 更易于阅读且更直观。我们有一个定义 = 一个声明 = 在代码中查看一个位置。

注意:模块是相当新的,开发者社区尚未建立最佳实践

对性能有任何技术影响吗?

我无法谈论性能,因为我没有测试过。目前,没有主要的编译器供应商支持可以进行压力测试的大规模模块。

A.代码库分为多少个模块?

每个编译单元可能有一个模块声明,即每个源文件都声明一个模块。唯一的例外是内部头文件或项目的主文件。如果一个源文件没有声明一个模块,那么它需要一个相应的头文件以便可以使用它的定义,这消除了模块的用途。

B.对于每个模块,该模块分为多少个源文件?

这取决于。模块与命名空间完全正交(即不相关的概念)。在模块接口单元内,我们限制符号仅在以 export 为前缀或放置在 export { } 范围内时导出。

(有人建议“模块在大块中效果最好”?为什么?)

这很可能是因为我们不需要在使用源文件时使用大量的import。在模块化项目中,每个源文件都会声明一个模块,否则我们需要将它们的定义包含在相应的头文件中。所以不用写import A; import B; import C; import D; import E;,我们可以让模块B-E成为A的模块分区:A:BA:C等,然后只有import A;,同时仍然隐式导入B-E。

C.如果 B 的答案不止一个,那么这三种(1,2 或 3)中的哪一种是多余的文件?

这取决于您是否希望这些辅助文件的内容具有全局可见性。考虑这个设置:

// module_a.cpp
export module A;
import :B;
[...]

// module_b.cpp
module A:B;
[...]

模块 B 中的所有内容只能在模块 A 中可见。但是,如果您希望这些内容在 A 外部可见,则 B 必须导出自身,而 A 必须导出-导入 B:

// module_a.cpp
export module A;
export import :B;
[...]

// module_b.cpp
export module A:B;
int f(int x) { return x + 1; } // only visible inside A
export g(int x) { return x + 2; } // visible outside A
[...]

【讨论】:

  • 您确定您的解释是基于 C++20 模块规范而不是一些不符合 C++20 之前的实现吗?您的开头段落似乎暗示一个模块不能同时具有主模块接口单元和模块实现单元(即不是模块分区)。这与我对[module.unit] 的阅读相反
  • @AndrewTomazos 我犯了一个可怕的错误,即使阅读了这么多文档页面,但仍然错过了如此重要的细节:/。当然,可能有一个不导出的模块单元。我会更新我的帖子。非常感谢您让我知道!
猜你喜欢
  • 1970-01-01
  • 2019-11-27
  • 1970-01-01
  • 2016-05-28
  • 1970-01-01
  • 2015-01-11
  • 1970-01-01
  • 1970-01-01
  • 2017-11-11
相关资源
最近更新 更多