【问题标题】:Forward declaration to break cyclic dependency in C++20 modules doesn't work在 C++20 模块中打破循环依赖的前向声明不起作用
【发布时间】:2020-07-08 13:29:55
【问题描述】:

几天来我一直在努力解决这个问题,我阅读了很多关于新 C++20 模块的文档和帖子,其中 this official onethis onethis other one on Stackoverflow,但我真的无法解决这个问题。

我正在使用随 Visual Studio Preview 16.6.0 2.0 提供的 MSVC 编译器。我知道它还不是一个稳定的版本,但我想尝试一些新功能来开始学习它们。

基本上我写了一个模块(myModule)和这个模块的两个分区(mySubmodule1mySubmodule2),我在两个模块实现文件(mySubmodule1Impl.cppmySubmodule2Impl.cpp)中实现了它们。

mySubmodule1 依赖于mySubmodule2,反之亦然。以下是出处:

mySubmodule1.ixx

export module myModule:mySubmodule1;

export namespace myNamespace{

class MyClass2;

class MyClass1{
    public:
    int foo(MyClass2& c);
    int x = 9;
};
}

mySubmodule2.ixx

export module myModule:mySubmodule2;
import :mySubmodule1;

export namespace myNamespace{

class MyClass2 {
    public:
    MyClass2(MyClass1 x);
    int x = 14;
    MyClass1 c;
};
}

mySubmodule1Impl.cpp

module myModule:mySubmodule1;
import :mySubmodule2;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl.cpp

module myModule:mySubmodule2;
import :mySubmodule1;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

myModule.ixx

export module myModule;

export import :mySubmodule1;
export import :mySubmodule2;

如您所见,我可以在mySubmodule1 中转发声明MyClass2,但我不能在mySubmodule2 中转发声明MyClass1,因为在MyClass2 中我使用了MyClass1 类型的具体对象。

我用这一行编译:cl /EHsc /experimental:module /std:c++latest mySubmodule1.ixx mySubmodule2.ixx myModule.ixx mySubmodule1Impl.cpp mySubmodule2Impl.cpp Source.cpp 其中Source.cpp 只是主要的。

在我使用MyClass2 的行中,我在mySubmodule1Impl.cppmySubmodule2Impl.cpp 中收到了臭名昭著的error C2027: use of undefined type 'myNamespace::MyClass2'。此外,编译器告诉我在mySubmodule1.ixx 中查看MyClass2 的声明,其中有前向声明。

现在,我真的不明白我的错误在哪里。我一遍又一遍地检查,但程序的逻辑对我来说似乎很完美。文件的编译顺序应该在实现之前定义MyClass2

我尝试使用“旧”.h 和 .cpp 文件而不是模块来编译这个确切的程序,它编译并运行良好。所以我想我错过了关于这些新模块的一些东西。

我查看了first official proposal of modules (paragraph 10.7.5),在第一个中,有一个名为宣布所有权声明的构造,在这种情况下似乎是完美的。基本上,它允许您导入当前模块中另一个模块拥有的实体,但不导入模块本身。但是在later revisions of the proposal 中没有任何迹象。绝对没有。而且在新提案的“变更日志”部分甚至没有被引用。

请不要告诉我循环依赖是不好的。我经常知道它们很糟糕,但并非总是如此。即使您认为它们总是很糟糕,我也不会要求经验法则。我在问为什么我的代码用“旧” .h + .cpp 编译而不是新模块。为什么链接器看不到MyClass2的定义。


编辑 1

这是答案中建议的新设计,但它仍然不起作用。我得到完全相同的错误:

mySubmodule1Impl.cpp

module myModule;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl.cpp

module myModule;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

所有其他文件均未更改。

【问题讨论】:

  • 此代码中的一些使用mySubmodule 和一些mySubmodule1。这是什么?
  • @T.C.对不起,你是对的,它是一样的。在我的代码中,我使用了 mySubmodule,但我认为在示例中将其命名为 mySubmodule1 会更好,以避免误解。我可能忘记更改一些事件。现在我纠正它们,一切都应该没问题。
  • @T.C.嗯,我没有两个同名的模块分区。顺便说一句,如果我删除循环依赖,它就可以工作,所以我认为问题不在于名称,而在于循环依赖本身。谢谢!
  • 请注意,这整个问题与使用分区无关。我不使用分区,并且两个模块中的两个类相互引用,因此 forward declare 彼此存在完全相同的问题。编译任一模块都会抱怨“类不完整”并引用前向声明 - 而不是使用完整的类定义。有时它甚至抱怨类 'namespace::A' 不能转换为 'namespace::A' - 显然在内部有它的两个实例。

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


【解决方案1】:

直接的问题是你不能有一个“接口文件”一个单个模块分区的“实现文件”(就好像它是一个头文件一样)文件和源文件对)。有接口分区和实现分区,但每个分区都必须有自己的名称,因为每个分区都存在以便导入。当然,模块的目的之一也是允许 single 文件需要头/源对:您通常可以将实现包含在与接口文件相同的文件中,但使用@987654322 @ 和/或 inline 仅适用于后者。这确实带来了导致更频繁的下游重建的通常只有标头的缺点。

元问题是这里没有循环性:您已经通过MyClass2 的前向声明解决了它。这是正确的做法:模块不会改变 C++ 的基本语义,因此这些技术仍然适用且必要。出于通常的组织原因,您仍然可以将类分成两个 文件,但方法定义根本不需要位于 partitions 中(也不需要单独的 module myModule;实现单元,自动导入所有接口)。剩下的import :mySubmodule1(在接口分区mySubmodule2中)是明确且正确的。

至于proclaimed-ownership-declaration,它们出现在没有模块分区的Modules TS中,这样的情况无法处理(因为您可以对来自另一个 partition 但不能来自另一个 module 的实体使用正常的前向声明。

【讨论】:

  • 好的,首先,非常感谢!我承认我现在有点困惑,我有很多问题,但特别是 2:1) 为什么,如果我删除 mySubmodule2.ixx 中对 MyClass1 的所有引用,一切正常(即使我有“接口文件”和“实现文件”)? 2) 如何将分区的接口与其实现分开?我认为这在设计方面可能非常有用,但现在我真的想不出一个解决方案......我认为分区对于将模块的逻辑部分划分为更小的文件很有用,但如果我不能将它们的实现分开,它们'再没用...不是吗?
  • @Lapo:违反有关翻译单元的规则不需要诊断:编译器可能不会检查其他文件或由于单独处理而未检测到冲突。您应该将实现放在普通的实现单元中(只需module A;);您不能(应该)能够在仅导入它的翻译单元中定义from模块。 (您已经找到了经批准的原始措辞,但我确实也写了a little bit more on modules。)
  • @Lapo:您可以拥有尽可能多的分区(每个分区在其[export] module foo:…; 中都有一个不同的 )和尽可能多的“简单实现单元”(全部带有same module foo;) 就像你想要的一个 foo。 (显然每一种都有一个文件名称。)
  • @Lapo:太好了;不客气。有一些希望,对实现和约定的改进将减少这里无声的不当行为的可能性,但单独的翻译从根本上很难使用户友好。
  • @Lapo:据我所知,修改后的代码是正确的。确保您没有使用任何旧的编译模块接口文件——我不知道这些文件如何与 MSVC 一起使用。
【解决方案2】:

查看我对this post 的回复。您可能需要导出您的前向声明以获得MyClass2 的外部链接而不是模块链接。

【讨论】:

    【解决方案3】:

    当然。如果我能够直接回复帖子,那么答案在上下文中更有意义。上面的链接是对 Touloudou 的最后一个答案的回复,并附有额外的参考资料。该解决方案:从分区导出前向声明(可选择将导出分隔到它们自己的分区中)。

    此外,在撰写该网站帖子时,禁止跨模块的交叉引用并且 gcc 支持滞后,这可以解释当时其他早期答案遇到的问题和可能的误导。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-18
      • 1970-01-01
      • 2012-10-08
      • 2021-09-17
      • 1970-01-01
      • 2012-04-24
      • 2010-12-17
      • 1970-01-01
      相关资源
      最近更新 更多