简体   繁体   English

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

[英]Forward declaration to break cyclic dependency in C++20 modules doesn't work

I've been banging my head on this problem for days, I read a lot of documentation and posts about new C++20 modules among which this official one , this one and this other one on Stackoverflow , but I really cannot solve this problem.几天来我一直在努力解决这个问题,我阅读了很多关于新 C++20 模块的文档和帖子,其中这个官方模块,这个另一个在 Stackoverflow 上,但我真的无法解决这个问题.

I'm using MSVC compiler delivered with Visual Studio Preview 16.6.0 2.0 .我正在使用随Visual Studio Preview 16.6.0 2.0提供的 MSVC 编译器。 I know it is not a stable release yet, but I'd like to mess around with new features to start learning them.我知道它还不是一个稳定的版本,但我想弄乱新功能来开始学习它们。

Basically I wrote a module ( myModule ) and 2 partitions of this module ( mySubmodule1 and mySubmodule2 ) and I implemented them in two module implementation files ( mySubmodule1Impl.cpp and mySubmodule2Impl.cpp ).基本上我写了一个模块( myModule )和这个模块的 2 个分区( mySubmodule1mySubmodule2 ),我在两个模块实现文件( mySubmodule1Impl.cppmySubmodule2Impl.cpp )中实现了它们。

mySubmodule1 have a dependency on mySubmodule2 , and vice-versa. mySubmodule1依赖于mySubmodule2 ,反之亦然。 Here is the source:这是来源:

mySubmodule1.ixx mySubmodule1.ixx

export module myModule:mySubmodule1;

export namespace myNamespace{

class MyClass2;

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

mySubmodule2.ixx mySubmodule2.ixx

export module myModule:mySubmodule2;
import :mySubmodule1;

export namespace myNamespace{

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

mySubmodule1Impl.cpp mySubmodule1Impl.cpp

module myModule:mySubmodule1;
import :mySubmodule2;

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

mySubmodule2Impl.cpp mySubmodule2Impl.cpp

module myModule:mySubmodule2;
import :mySubmodule1;

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

myModule.ixx我的模块.ixx

export module myModule;

export import :mySubmodule1;
export import :mySubmodule2;

As you can see I can forward declare MyClass2 in mySubmodule1 , but I cannot forward declare MyClass1 in mySubmodule2 , because in MyClass2 I use a concrete object of type MyClass1 .如您所见,我可以在mySubmodule1中转发声明MyClass2 ,但不能在mySubmodule2中转发声明MyClass1 ,因为在MyClass2中我使用了MyClass1类型的具体对象。

I compile with this line: cl /EHsc /experimental:module /std:c++latest mySubmodule1.ixx mySubmodule2.ixx myModule.ixx mySubmodule1Impl.cpp mySubmodule2Impl.cpp Source.cpp where Source.cpp is just the main.我用这一行编译: cl /EHsc /experimental:module /std:c++latest mySubmodule1.ixx mySubmodule2.ixx myModule.ixx mySubmodule1Impl.cpp mySubmodule2Impl.cpp Source.cpp其中Source.cpp只是主要的。

I get the infamous error C2027: use of undefined type 'myNamespace::MyClass2' in mySubmodule1Impl.cpp and mySubmodule2Impl.cpp at the lines where I use MyClass2 .我收到臭名昭著的错误 C2027: use of undefined type 'myNamespace::MyClass2' in mySubmodule1Impl.cppmySubmodule2Impl.cpp在我使用MyClass2的行。 Moreover the compiler tells me to look at the declaration of MyClass2 in mySubmodule1.ixx where there is the forward declaration.此外,编译器告诉我在MyClass2mySubmodule1.ixx的声明,其中有前向声明。

Now, I really do not understand where is my mistake.现在,我真的不明白我的错误在哪里。 I checked over and over but the logic of the program seems perfect to me.我一遍又一遍地检查,但程序的逻辑对我来说似乎很完美。 The order of compilation of the files should define MyClass2 before it is even used in the implementation!文件的编译顺序应该在MyClass2甚至在实现中使用之前定义它!

I tried to compile this exact program using the "old" .h and .cpp files instead of modules, and it compiles and run fine.我尝试使用“旧”.h 和 .cpp 文件而不是模块来编译这个确切的程序,它编译并运行良好。 So I guess I'm missing something regarding these new modules.所以我想我错过了关于这些新模块的一些东西。

I checked on the first official proposal of modules (paragraph 10.7.5) , and in the first one there was a construct named proclaimed ownership declaration which seemed to be perfect in such cases.我检查了第一个正式的模块提案(第 10.7.5 段) ,在第一个提案中有一个名为宣告所有权声明的构造,在这种情况下似乎是完美的。 Basically it allows you to import an entity owned by another module in the current module, but without importing the module itself.基本上,它允许您导入当前模块中另一个模块拥有的实体,但不导入模块本身。 But in later revisions of the proposal there is no sign of it.但在该提案的后期修订中,没有任何迹象。 Abslolutely nothing.绝对没有。 And in the "changelog" section of the new proposal it isn't even cited.在新提案的“变更日志”部分甚至没有引用它。

Please don't tell me cyclic dependencies are bad.请不要告诉我循环依赖是不好的。 I know often they are bad, but not always.我经常知道它们很糟糕,但并非总是如此。 And even if you think they are always bad I'm not asking for a rule of the thumb.即使您认为它们总是很糟糕,我也不会要求经验法则。 I'm asking why my code compiles with "old" .h + .cpp but not with new modules.我在问为什么我的代码用“旧” .h + .cpp 编译而不是新模块。 Why the linker doesn't see the definition of MyClass2 .为什么链接器看不到MyClass2的定义。


EDIT 1编辑 1

Here is the new design suggested in the answer, but it still doesn't work.这是答案中建议的新设计,但它仍然不起作用。 I get the exact same errors:我得到完全相同的错误:

mySubmodule1Impl.cpp mySubmodule1Impl.cpp

module myModule;

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

mySubmodule2Impl.cpp mySubmodule2Impl.cpp

module myModule;

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

All of the other files are unchanged.所有其他文件均未更改。

The immediate problem is that you can't have an “interface file” and an “implementation file” for a single module partition (as if it were a header file and source file pair).最直接的问题是你不能为单个模块分区拥有一个“接口文件”一个“实现文件”(就好像它是一个头文件和源文件对一样)。 There are interface partitions and implementation partitions, but each must have its own name because each exists to be imported .有接口分区和实现分区,但每个分区都必须有自己的名称,因为每个分区都需要导入 Of course, it is also one of the purposes of modules to allow a single file where header/source pairs were needed: you can often include the implementation in the same file as the interface file but use export and/or inline only with the latter.当然,模块的目的之一也是允许需要头/源对的单个文件:您通常可以将实现包含在与接口文件相同的文件中,但仅在后者中使用export和/或inline . This does come with the usual header-only downside of causing more frequent downstream rebuilds.这确实带来了导致更频繁的下游重建的通常仅标题的缺点。

The metaproblem is that there is no circularity here: you've already addressed it with the forward declaration of MyClass2 .元问题是这里没有循环:您已经通过MyClass2的前向声明解决了它。 That's the right thing to do: modules don't change the basic semantics of C++, so such techniques remain applicable and necessary.这是正确的做法:模块不会改变 C++ 的基本语义,因此这些技术仍然适用且必要。 You can still divide the classes into two files for the usual organizational reasons, but there's no need for the method definitions to be in partitions at all (nor in separate module myModule; implementation units, which automatically import all of the interface).出于通常的组织原因,您仍然可以将类分成两个文件,但方法定义根本不需要在分区中(也不需要在单独的module myModule;实现单元中,它会自动导入所有接口)。 The import :mySubmodule1 that remains (in the interface partition mySubmodule2 ) is then unambiguous and correct.保留的import :mySubmodule1 (在接口分区mySubmodule2 )是明确且正确的。

As for proclaimed-ownership-declaration s, they appeared in the Modules TS that didn't have module partitions, such that cases like this could not be handled otherwise (since you can use a normal forward declaration for an entity from another partition but not another module ).至于proclaimed-ownership-declaration ,它们出现在没有模块分区的 Modules TS 中,因此无法以其他方式处理此类情况(因为您可以对来自另一个分区的实体使用正常的前向声明,但不能另一个模块)。

Try exporting the forward declarations.尝试导出前向声明。 eg例如

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

See my answer to this post .请参阅我对这篇文章的回答。 You probably need to export your forward declaration to have external rather than module linkage for MyClass2 .您可能需要导出前向声明以获得MyClass2的外部链接而不是模块链接。

In addition: very helpful in depth answer.另外:深入回答非常有帮助。 The helpfulness can not be overstated.乐于助人不能被夸大。

Sure.当然。 If I could have had the ability to directly reply to posts, the answer makes more sense in context.如果我能够直接回复帖子,那么答案在上下文中更有意义。 The link above is meant as post-reponse to Touloudou's last answer with additional references.上面的链接是对 Touloudou 最后一个答案的回复,并附有额外的参考资料。 That solution: export forward declarations from partitions (optionally separating exports into their own partition).该解决方案:从分区导出前向声明(可选地将导出分隔到它们自己的分区中)。

Additionally, at the time that site post was written, cross references across modules were prohibited and gcc support was lagging, which could explain the problems and possible misdirection encountered from other earlier answers, at the time.此外,在撰写该网站帖子时,禁止跨模块交叉引用并且 gcc 支持滞后,这可以解释当时其他早期答案中遇到的问题和可能的误导。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM