7

我最近一直在尝试使用 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
}

(这里Bh的内容不重要)

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

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

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

  • 有人说有一个带有前向声明的模块。正如我上面所说,这不起作用。
  • 有人说要使用宣布的所有权声明。但是,它们已从最终的 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:在标头中前向声明,在模块中定义

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;
4

3 回答 3

1

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

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

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

于 2021-06-12T21:43:15.283 回答
0

我的猜测是这与模块链接有关。当您转发 declareB时,该名称默认具有模块链接。因此,它B与模块中定义的不同B(因此是未定义的类型)。如果您导出前向声明,您最终将获得外部链接而不是模块链接,这应该可以解决您的问题。

在实践中,这意味着A.mpp在您的第一次尝试中改变

export module A;

export class B;

export class A {
    public:
        void f(B& b);
};

如果您想了解更多信息, vector-of-bool 就该主题写了一篇有趣的帖子。

于 2021-10-07T09:23:07.143 回答
0

我不敢相信似乎没有任何好的解决方法。无论如何,例如打破循环依赖的一种绝望的解决方案是使用模板(这没有回答关于如何转发声明的问题,只是在一种情况下如何避免需要):

// A_impl.cc

export module A_impl;

export template <typename B> class A_impl {
    public:
        void f(B& b) {}
};
// B.cc

export module B;

import A_impl;

export class B;

typedef A_impl<B> A;

export class B {
    public:
        void f(A& a) {}
};
// A.cc

export module A;

export import A_impl;
import B;

export typedef A_impl<B> A;
// main.cc

import A;
import B;

int main(void) {
    A a;
    B b;

    a.f(b);
    b.f(a);

    return 0;
}

目前 clang 不支持模块分区,因此使用该工具链这似乎是在不同文件中定义 A 和 B (没有#include)同时将它们放置在模块中的唯一方法。

于 2021-12-07T23:31:55.733 回答