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

Go 包循环依赖如何分析 import cycle not allowed

Maven多模块项目循环依赖问题

Python中的循环模块依赖和相对导入

前端模块化之循环加载

Spring Boot (kotlin) 循环依赖