我已经在这个问题上绞尽脑汁了好几天,我读了很多关于新的C++20模块的文档和帖子,其中包括this official one,this one和this other one on Stackoverflow,但我真的不能解决这个问题。
我使用的是随 Visual Studio Preview 16.6.0 2.0 提供的MSVC编译器。我知道它还不是一个稳定的版本,但我想在新功能周围乱转,开始学习它们。
基本上,我写了一个模块(myModule
)和这个模块的两个分区(mySubmodule1
和mySubmodule2
),并在两个模块实现文件(mySubmodule1Impl.cpp
和mySubmodule2Impl.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.cpp
和mySubmodule2Impl.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;
}
型
所有其他文件均未更改。
4条答案
按热度按时间ozxc1zmp1#
最直接的问题是,你不能有一个“接口文件”* 和 * 一个“实现文件”用于单个模块分区(就像它是一个头文件和源文件对一样)。有接口分区和实现分区,但每一个都必须有自己的名称,因为每一个都是为了被导入而存在的。当然,模块的目的之一是允许一个单个文件,其中需要头/源对:你通常可以在同一个文件中包含接口的实现,但只能在接口中使用
export
和/或inline
。这确实伴随着通常的仅报头的缺点,即导致更频繁的下游重建。元问题是这里没有循环性:你已经用
MyClass2
的forward声明解决了它。这才是正确的做法模块并没有改变C++的基本语义,因此这些技术仍然是适用的和必要的。出于通常的组织原因,您仍然可以将类划分为两个文件,但根本不需要将方法定义放在分区中(也不需要在单独的module myModule;
实现单元中,这些单元 * 自动 * 导入所有接口)。剩下的import :mySubmodule1
(在接口分区mySubmodule2
中)是明确和正确的。至于 proclaimed-ownership-declarations,它们出现在没有模块分区的模块TS中,这样的情况就无法以其他方式处理(因为您可以对来自另一个 * 分区 * 但不是另一个 * 模块 * 的实体使用正常的前向声明)。
pxyaymoc2#
尝试导出转发声明。例如,在
wbgh16ku3#
看我对这篇文章的回答。您可能需要导出forward声明,以便为
MyClass2
提供外部链接而不是模块链接。6ljaweal4#
当然。如果我能直接回复帖子,答案就更有意义了。上面的链接是对Touloudou最后一次回答的回应,并附有额外的参考资料。该解决方案:从分区导出转发声明(可选地将导出分隔到它们自己的分区中)。
此外,在写这篇文章的时候,跨模块的交叉引用是被禁止的,gcc的支持是滞后的,这可以解释当时其他早期答案中遇到的问题和可能的误导。