问题描述
我最近一直在尝试使用 GCC 11 将代码库转换为 C++20 模块。但是,我遇到了以下情况。首先,这是如何使用标头完成的:
啊
class B;
class A {
public:
void f(B& b);
};
A.cpp
#include "A.h"
#include "B.h"
void A::f(B& b)
{
// do stuff with b
}
(这里B.h的内容不重要)
需要注意的是 B 的前向声明。并不是每个使用 A 的人都应该关心 B,所以我使用了前向声明来阻止重新编译的发生。使用标题,这种情况非常适用。
问题在于尝试将此代码转换为模块时。主要问题是实体与它们在其中声明的模块相关联,因此不可能在 A.h 中进行前向声明。我尝试在全局模块中向前声明,但是编译器仍然抱怨 B 的定义与其声明位于不同的模块中。我还尝试使用仅包含 B 的前向声明的第三个模块,但这仍然是在两个不同的模块中声明和定义 B。所以,我的主要问题是:我怎样才能从模块外部的模块中转发声明某些东西?我也会对最终产生相同效果的方式感到满意:A 的用户不会B变化时需要重新编译。
在搜索的过程中,我发现有几个地方在谈论类似的情况,但由于某种原因它们都不起作用。它们不起作用的原因:
- 有人说有一个带有前向声明的模块。正如我上面所说,这行不通。
- 有人说使用声明的所有权声明。但是,它们已从最终的 C++ 标准中删除。
- 有人说使用模块分区。但是,这只适用于 A 和 B 在同一个模块中的情况。 A 和 B 不应该在同一个模块中,所以这不起作用。
尝试 1:在 A.mpp 中向前声明 B
A.mpp
export module A;
class B;
export class A {
public:
void f(B& b);
};
B.mpp
export module B;
export class B {};
A.cpp
module A;
import B;
void A::f(B& b)
{
// do stuff with b
}
这样做时,gcc 会出错
A.cpp:4:11: error: reference to ‘B’ is ambiguous
4 | void A::f(B& b)
| ^
In module B,imported at A.cpp:2:
B.mpp:3:14: note: candidates are: ‘class B@B’
3 | export class B {};
| ^
In module A,imported at A.cpp:1:
A.mpp:3:7: note: ‘class B@A’
3 | class B;
尝试 2:在新模块中向前声明
B_decl.mpp
export module B_decl;
export class B;
A.mpp
export module A;
import B_decl;
export class A {
public:
void f(B& b);
};
B.mpp
export module B;
import B_decl;
class B {};
A.mpp
module A;
import B;
void A::f(B& b)
{
// do stuff with b
}
这样做时,gcc 会出错
B.mpp:5:14: error: cannot declare ‘class B@B_decl’ in a different module
5 | class B {};
| ^
In module B_decl,imported at B.mpp:3:
B_decl.mpp:3:14: note: declared here
3 | export class B;
尝试3:在header中向前声明,在模块中定义
B_decl.h
class B;
A.mpp
module;
#include "B_decl.h"
export module A;
export class A {
public:
void f(B& b);
};
B.mpp
module;
#include "B_decl.h"
export module B;
class B {};
A.cpp
module A;
import B;
void A::f(B& b)
{
// do stuff with b
}
这样做时,gcc 会出错
B.mpp:7:7: error: cannot declare ‘class B’ in a different module
7 | class B {};
| ^
In file included from B.mpp:3:
B_decl.h:1:7: note: declared here
1 | class B;
解决方法
对此的解决方案取决于您首先要转发声明的原因。
如果您这样做是为了打破循环依赖,那么通常的解决方案是将它们简单地放在同一个模块中。由于组件如此紧密地耦合在一起,因此将它们放在同一个模块中是有意义的。
如果你这样做是为了加快编译速度,你最好简单地导入模块并使用类型。导入模块几乎没有成本。编译模块有,而且只做了一次。