C++ 模块从另一个模块转发声明实体

sud*_*dgy 11 c++ forward-declaration c++20 c++-modules gcc11

我最近一直在尝试使用 GCC 11 将代码库转换为 C++20 模块。但是,我遇到了以下情况。首先,这是使用标头完成的方法:

\n

\n
class B;\n\nclass A {\n    public:\n        void f(B& b);\n};\n
Run Code Online (Sandbox Code Playgroud)\n

a.cpp

\n
#include "A.h"\n#include "B.h"\n\nvoid A::f(B& b)\n{\n    // do stuff with b\n}\n
Run Code Online (Sandbox Code Playgroud)\n

(Bh的内容在这里并不重要)

\n

需要注意的是 B 的前向声明。并不是每个使用 A 的人都应该关心 B,所以我使用前向声明来阻止重新编译的发生。有了标题,这种情况就完美了。

\n

问题在于尝试将此代码转换为模块时。主要问题是实体与声明它们的模块相关联,因此在 Ah 中向前声明是不可能的。我尝试在全局模块中进行前向声明,但编译器仍然抱怨 B 的定义与其声明位于不同的模块中。我还尝试使用第三个模块,其中仅包含 B 的前向声明,但这仍然是在两个不同的模块中声明和定义 B。所以,我的主要问题是:如何从模块外部转发声明某些内容? 我也会对最终产生相同效果的方式感到满意:当 B 更改时,A 的用户不需要重新编译。

\n

在搜索时,我发现了一些地方谈论类似的情况,但由于某种原因它们都不起作用。他们不起作用的原因:

\n
    \n
  • 有人说有一个带有前向声明的模块。正如我上面所说,这是行不通的。
  • \n
  • 有人说要使用声明的所有权声明。然而,它们已从最终的 C++ 标准中删除。
  • \n
  • 有人说使用模块分区。然而,这仅当 A 和 B 位于同一模块中时才有效。A 和 B 不应该在同一个模块中,所以这是行不通的。
  • \n
\n

编辑:为了回应评论,以下是我尝试过的一些事情的详细信息:

\n

尝试 1:在 A.mpp 中转发声明 B

\n

mpp

\n
export module A;\n\nclass B;\n\nexport class A {\n    public:\n        void f(B& b);\n};\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
export module B;\n\nexport class B {};\n
Run Code Online (Sandbox Code Playgroud)\n

a.cpp

\n
module A;\n\nimport B;\n\nvoid A::f(B& b)\n{\n    // do stuff with b\n}\n
Run Code Online (Sandbox Code Playgroud)\n

执行此操作时,gcc 会出现错误

\n
A.cpp:4:11: error: reference to \xe2\x80\x98B\xe2\x80\x99 is ambiguous\n    4 | void A::f(B& b)\n      |           ^\nIn module B, imported at A.cpp:2:\nB.mpp:3:14: note: candidates are: \xe2\x80\x98class B@B\xe2\x80\x99\n    3 | export class B {};\n      |              ^\nIn module A, imported at A.cpp:1:\nA.mpp:3:7: note:                 \xe2\x80\x98class B@A\xe2\x80\x99\n    3 | class B;\n
Run Code Online (Sandbox Code Playgroud)\n

尝试2:在新模块中转发声明

\n

B_decl.mpp

\n
A.cpp:4:11: error: reference to \xe2\x80\x98B\xe2\x80\x99 is ambiguous\n    4 | void A::f(B& b)\n      |           ^\nIn module B, imported at A.cpp:2:\nB.mpp:3:14: note: candidates are: \xe2\x80\x98class B@B\xe2\x80\x99\n    3 | export class B {};\n      |              ^\nIn module A, imported at A.cpp:1:\nA.mpp:3:7: note:                 \xe2\x80\x98class B@A\xe2\x80\x99\n    3 | class B;\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
export module B_decl;\n\nexport class B;\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
export module A;\n\nimport B_decl;\n\nexport class A {\n    public:\n        void f(B& b);\n};\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
export module B;\n\nimport B_decl;\n\nclass B {};\n
Run Code Online (Sandbox Code Playgroud)\n

执行此操作时,gcc 会出现错误

\n
B.mpp:5:14: error: cannot declare \xe2\x80\x98class B@B_decl\xe2\x80\x99 in a different module\n    5 | class B {};\n      |              ^\nIn module B_decl, imported at B.mpp:3:\nB_decl.mpp:3:14: note: declared here\n    3 | export class B;\n
Run Code Online (Sandbox Code Playgroud)\n

尝试3:在头文件中转发声明,在模块中定义

\n

B_decl.h

\n
module A;\n\nimport B;\n\nvoid A::f(B& b)\n{\n    // do stuff with b\n}\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
B.mpp:5:14: error: cannot declare \xe2\x80\x98class B@B_decl\xe2\x80\x99 in a different module\n    5 | class B {};\n      |              ^\nIn module B_decl, imported at B.mpp:3:\nB_decl.mpp:3:14: note: declared here\n    3 | export class B;\n
Run Code Online (Sandbox Code Playgroud)\n

mpp

\n
class B;\n
Run Code Online (Sandbox Code Playgroud)\n

a.cpp

\n
module;\n\n#include "B_decl.h"\n\nexport module A;\n\nexport class A {\n    public:\n        void f(B& b);\n};\n
Run Code Online (Sandbox Code Playgroud)\n

执行此操作时,gcc 会出现错误

\n
B.mpp:7:7: error: cannot declare \xe2\x80\x98class B\xe2\x80\x99 in a different module\n    7 | class B {};\n      |       ^\nIn file included from B.mpp:3:\nB_decl.h:1:7: note: declared here\n    1 | class B;\n
Run Code Online (Sandbox Code Playgroud)\n

Gui*_*cot 2

解决方案取决于您首先要转发声明的原因。

如果您这样做是为了打破循环依赖关系,那么通常的解决方案是将它们放在同一个模块中。由于这些组件如此紧密地耦合在一起,因此将它们放在同一个模块中是有意义的。

如果您这样做是为了加快编译速度,那么最好简单地导入模块并使用该类型。导入模块几乎没有成本。编译模块已经完成,而且只完成了一次。