在C++20模块中中断循环依赖的前向声明不起作用

pprl5pva  于 2023-08-09  发布在  其他
关注(0)|答案(4)|浏览(91)

我已经在这个问题上绞尽脑汁了好几天,我读了很多关于新的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只是主节点。
我得到了臭名昭著的**错误C2027:在mySubmodule1Impl.cppmySubmodule2Impl.cpp中使用未定义的类型'myNamespace::MyClass 2'*在我使用MyClass2的行中。此外,编译器告诉我在mySubmodule1.ixx中查看MyClass2的声明,其中有forward声明。
现在,我真的不明白我错在哪里。我检查了一遍又一遍,但程序的逻辑对我来说似乎是完美的。文件的编译顺序应该在实现中使用MyClass2之前定义它!
我尝试使用“旧的”.h和.cpp文件而不是模块来编译这个精确的程序,它编译和运行良好。所以我想我错过了一些关于这些新模块的东西。
我检查了first official proposal of modules (paragraph 10.7.5),在第一个中有一个名为 * prompted ownership declaration
的结构,在这种情况下似乎很完美。基本上,它允许您导入当前模块中另一个模块所拥有的实体,但不导入模块本身。但是在later revisions of the proposal中没有任何迹象。完全没有。在新提案的“更新日志”部分,甚至没有引用它。
请不要告诉我循环依赖是不好的。我知道他们经常是坏的,但并不总是。即使你认为它们总是不好的,我也不是在要求一个经验法则。我在问为什么我的代码可以用“old”.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;
}


所有其他文件均未更改。

ozxc1zmp

ozxc1zmp1#

最直接的问题是,你不能有一个“接口文件”* 和 * 一个“实现文件”用于单个模块分区(就像它是一个头文件和源文件对一样)。有接口分区和实现分区,但每一个都必须有自己的名称,因为每一个都是为了被导入而存在的。当然,模块的目的之一是允许一个单个文件,其中需要头/源对:你通常可以在同一个文件中包含接口的实现,但只能在接口中使用export和/或inline。这确实伴随着通常的仅报头的缺点,即导致更频繁的下游重建。
元问题是这里没有循环性:你已经用MyClass2的forward声明解决了它。这才是正确的做法模块并没有改变C++的基本语义,因此这些技术仍然是适用的和必要的。出于通常的组织原因,您仍然可以将类划分为两个文件,但根本不需要将方法定义放在分区中(也不需要在单独的module myModule;实现单元中,这些单元 * 自动 * 导入所有接口)。剩下的import :mySubmodule1(在接口分区mySubmodule2中)是明确和正确的。
至于 proclaimed-ownership-declarations,它们出现在没有模块分区的模块TS中,这样的情况就无法以其他方式处理(因为您可以对来自另一个 * 分区 * 但不是另一个 * 模块 * 的实体使用正常的前向声明)。

pxyaymoc

pxyaymoc2#

尝试导出转发声明。例如,在

// A.cc

export module Cyclic:A;

export class B;
export class A {
public:
    char name() { return 'A'; }
    void f(B& b);
};
// B.cc

export module Cyclic:B;

export class A;
export class B {
public:
    char name() { return 'B'; }
    void f(A& a);
};
// A_impl.cc

module Cyclic;

import Cyclic:A;
import Cyclic:B;

import <iostream>;

void A::f(B& b) {
  std::cout << name() << " calling " << b.name() << std::endl;
}
// B_impl.cc

module Cyclic;

import Cyclic:B;
import Cyclic:A;

import <iostream>;

void B::f(A& a) {
  std::cout << name() << " calling " << a.name() << std::endl;
}
// Cyclic.cc

export module Cyclic;
export import :A;
export import :B;
wbgh16ku

wbgh16ku3#

看我对这篇文章的回答。您可能需要导出forward声明,以便为MyClass2提供外部链接而不是模块链接。

6ljaweal

6ljaweal4#

当然。如果我能直接回复帖子,答案就更有意义了。上面的链接是对Touloudou最后一次回答的回应,并附有额外的参考资料。该解决方案:从分区导出转发声明(可选地将导出分隔到它们自己的分区中)。
此外,在写这篇文章的时候,跨模块的交叉引用是被禁止的,gcc的支持是滞后的,这可以解释当时其他早期答案中遇到的问题和可能的误导。

相关问题