C++20 模块“循环依赖”
Posted
技术标签:
【中文标题】C++20 模块“循环依赖”【英文标题】:C++20 Modules “cyclic dependency” 【发布时间】:2021-09-18 05:01:36 【问题描述】:我试图在另一个模块中包含一个模块,但由于以下错误,我无法编译:
“无法构建以下源文件,因为它们之间存在循环依赖关系:Module1.ixx 依赖于 Module2.ixx 依赖于 Module1.ixx。”
我希望 modClass1_ 包含 modClass2_ 类,而 modClass2_ 包含指向静态 modClass1_ 的指针。
我使用 C++17 头文件和源文件(.h 和 .cpp)成功尝试的代码
// Class1.h
#pragma once
#include "Class2.h"
class modClass2_;
class modClass1_
public:
modClass1_() ;
~modClass1_() ;
int V = 2;
int getV() return V; ;
static modClass2_ mc2;
;
extern modClass1_ mc1;
// Class1.cpp
#include "Class1.h"
modClass1_ mc1;
modClass2_ modClass1_::mc2;
// Class2.h
#pragma once
#include "Class1.h"
class modClass2_
public:
modClass2_() ;
~modClass2_() ;
int V = 1;
int getV() return V; ;
int getClass1V();
;
// Class2.cpp
#include "Class2.h"
int modClass2_::getClass1V()
return mc1.V;
// Main.cpp
#include "Class1.h"
#include <iostream>
int main()
std::cout << mc1.getV() << "\n"; // gets modClass1_ V directly
std::cout << mc1.mc2.getClass1V() << "\n"; // gets modClass1_ V through modClass2_ through modClass1_
std::cout << mc1.mc2.getV() << "\n"; // gets modClass2_ V through modClass1_
我尝试过但使用 C++20 模块 (.ixx) 失败的代码
// Module1.ixx
export module Module1;
import Module2;
export class modClass1_
public:
modClass1_() ;
~modClass1_() ;
int getV() return V; ;
modClass2_ mc2;
int getModule2V() return mc2.V; ;
int V = 1;
;
export modClass1_ mc1;
// Module2.ixx
export module Module2;
import Module1;
export class modClass2_
public:
modClass2_() ;
~modClass2_() ;
int getV() return V; ;
int getModule1V() return mc1.V; ;
int V = 2;
;
任何帮助/建议将不胜感激。
环境:Visual Studio 2019 | MSVC-2019 | C++20 |视窗 10 专业版
【问题讨论】:
为什么Class2.h
需要包含Class1.h
?要声明一个指针变量,你只需要一个前向声明。
循环依赖是不好的做法。好的代码不应该存在,除了特定情况(例如树类和节点定义在单个翻译单元中,一个是其他的实现细节)。
模块仍然可以有单独的接口和实现翻译单元。
@某程序员老兄,我尝试了前向声明,但模块无法编译。
@Nathan Pierson 你能给我一个例子如何在这些代码中实现单元吗?
【参考方案1】:
就像头文件一样,您可以将模块接口文件从模块实现文件中分离出来。示例:
模块1.ixx:
export module Module1;
import Module2;
export class modClass1_
public:
modClass1_() ;
~modClass1_() ;
int getV() return V; ;
modClass2_ mc2;
int getModule2V() return mc2.V; ;
int V = 1;
;
export modClass1_ mc1;
模块2.ixx:
export module Module2;
export class modClass2_
public:
modClass2_() ;
~modClass2_() ;
int getV() return V; ;
int getModule1V();
int V = 2;
;
模块2.cpp:
import Module1;
import Module2;
int modClass2_::getModule1V()
return mc1.V;
main.cpp:
#include <iostream>
import Module1;
import Module2;
int main()
// NB: mc1 is a symbol imported from Module1
std::cout << "mc1.V: " << mc1.V << '\n';
std::cout << "mc1.getModule2V: " << mc1.getModule2V() << '\n';
modClass2_ mc2;
std::cout << "mc2.V: " << mc2.V << '\n';
std::cout << "mc2.getModule1V: " << mc2.getModule1V() << '\n';
注意modClass2_
的接口不需要来自Module1
的任何东西,因此Module2.ixx
没有import Module1;
。 Module2.cpp
,实现所在的位置。
在我的示例中,我已尽可能少地从 Module2.ixx
移动到 Module2.cpp
实现文件,但实际上您可能希望将更多内容移出界面。
【讨论】:
无法摆脱拥有两个“Module2”文件?我想为每个文件保留一个模块。 @PedroDuarte 我认为,您可以将Module2.cpp
代码移动到module :private;
部分Module2.ixx
在 Visual Studio 2019 16.11.4 中,由于 Module1.ixx 和 Module2.ixx 之间的循环依赖关系,您的提案拒绝编译。我不确定私有模块片段的确切意义是什么,除了你可以通过从模块中拒绝export
得到什么,但它们仍然不允许你有这样的循环依赖。
Module2.cpp
需要有module Module2;
,而不是import Module2;
。【参考方案2】:
我有一个树形数据结构,分为两个需要相互引用的模块,以及posted an answer 如何使其工作。复制粘贴,这是一个通过使用模板打破循环依赖的绝望解决方案:
// 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
)同时将它们放置在模块中的唯一方法。使用 Visual Studio 模块分区可能会或可能不会允许更清晰的结构。
【讨论】:
以上是关于C++20 模块“循环依赖”的主要内容,如果未能解决你的问题,请参考以下文章
Go 包循环依赖如何分析 import cycle not allowed