C++20模块的“循环依赖”

3

我想在一个模块中包含另一个模块,但由于以下错误无法编译:

"因为存在循环依赖关系,所以无法构建以下源文件:Module1.ixx依赖于Module2.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 | Windows 10 Pro


1
为什么Class2.h需要包含Class1.h?声明指针变量只需要前向声明即可。 - Some programmer dude
1
循环依赖是不良实践。在良好的代码中,除了特定情况(例如在单个翻译单元中定义的Tree类和Node,其中一个是另一个的实现细节)外,不应存在循环依赖。 - Marek R
模块仍然可以有单独的接口和实现翻译单元。 - Nathan Pierson
@Some programmer dude,我尝试了前向声明,但是在模块中无法编译。 - Pedro Duarte
@Nathan Pierson,您能否给我一个如何在这些代码中实现单位的示例? - Pedro Duarte
class1.h 包含 #include "class2.h",而 class2.h 包含 #include "class1.h"。这是一个循环依赖,它根本无法工作。这与模块无关。 - Pete Becker
3个回答

5
就像头文件一样,您可以将模块接口文件与模块实现文件分开。例如:
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;

export class modClass2_
{
public:
  modClass2_() {};
  ~modClass2_() {};
  int getV() { return V; };
  int getModule1V();
  int V = 2;
};

Module2.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”文件吗?我想每个文件保留一个模块。 - Pedro Duarte
@PedroDuarte 我认为您可以将 Module2.cpp 的代码移到 Module2.ixxmodule: private; 部分。 - user362515
1
在 Visual Studio 2019 16.11.4 中,你的提案因 Module1.ixx 和 Module2.ixx 之间存在循环依赖而无法编译。我不确定私有模块片段的作用是什么,除了通过拒绝从模块中“导出”它们来获得的内容外,但它们仍然不允许你像这样有循环依赖。 - Nathan Pierson
1
Module2.cpp 需要有 module Module2;,而不是 import Module2; - Davis Herring

0
经过gcc测试,模块分割可以通过前向声明和内部模块链接来解决问题。注意:这并不是为了让模块彼此依赖,整个循环依赖关系是在一个单一模块中定义的。
// 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

import Cyclic:A;
import Cyclic:B;

import <iostream>;

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

// B_impl.cc

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;

请不要在多个问题上发布相同的答案,并且还有相同的错误。 - Davis Herring

0

我有一个树形数据结构分成两个模块,它们需要相互引用,发布了一个答案来解决这个问题。这里是一个绝望的解决方案,通过使用模板来打破循环依赖:

// 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不支持模块分区,因此使用该工具链似乎是在不使用#include的情况下在不同文件中定义A和B并将它们放置在模块中的唯一方法。使用Visual Studio模块分区可能允许更清晰的结构,也可能不允许。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接