【问题标题】:C++ Modules Forward Declaring Entity from Another ModuleC++ 模块从另一个模块转发声明实体
【发布时间】:2021-08-29 07:05:45
【问题描述】:

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

啊.h

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:在标头中前向声明,在模块中定义

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;

【问题讨论】:

  • "但这仍然是在两个不同的模块中声明和定义 B" 你怎么知道的?你遇到了什么错误?另外,您能否向我们展示您尝试过的所有模块案例的代码?您是否为这些声明使用了模块分区?
  • 我正要开始测试从头文件到模块的转换,但这是一个主要的阻碍。几乎所有我想要分离的东西都具有需要前向声明的依赖关系。

标签: c++ c++20 forward-declaration c++-modules gcc11


【解决方案1】:

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

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

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

【讨论】:

  • 我这样做是为了避免重新编译文件。导入的问题在于,这会导致在更改 B 时必须重新编译使用 A 的任何内容。导入模块基本上是免费的,但是重新编译文件不是。
  • @sudgy:如果您知道模块A 的接口中没有任何内容依赖于类B 的实现,那么您可能会跳过重新编译A 的客户端,尽管那显然,使用自动构建系统可能会带来不便,一些编译器可能会故意阻止它来检测错误。
【解决方案2】:

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

实际上,这意味着在您第一次尝试时更改 A.mpp

export module A;

export class B;

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

vector-of-bool 写了an interesting post关于这个话题如果你想了解更多。

【讨论】:

  • GCC 另有说明。它仍然给出了完全相同的错误,说对 B 的引用是模棱两可的。
  • 嗯,我正在使用 MSVC 16.11 进行测试。
  • 看来GCC在这种情况下是正确的。来自eel.is/c++draft/basic.link#10:“如果一个实体的两个声明附加到不同的模块,则程序格式错误。”并且声明附加到它们所在的模块(eel.is/c++draft/module#unit-7),导致在不同的模块中有两个B的声明,无论export是否存在。
【解决方案3】:

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

// 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)同时将它们放置在模块中的唯一方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-28
    • 2021-01-18
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 2012-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多