C++ 模块和循环类引用

Pet*_*aus 1 c++ c++20

为了了解有关 C++20 模块的更多信息,我正在将图形应用程序从头文件迁移到模块。目前我遇到两个类之间的循环依赖问题。这两个类描述了图的节点和边。边类具有指向两个节点的指针,并且节点类具有指向相邻边的指针向量。我知道,还有其他方法来描述图形,但这种架构对我来说似乎非常自然,我可以非常快速地访问相邻元素,并且它在头文件和#include 的旧世界中无缝工作。关键是前向引用。

但在 C++20 模块的新世界中,前向引用不再起作用。

循环引用的话题已经在很多地方讨论过,但我还没有找到真正让我信服的解决方案。

一个常见的说法是循环引用是一个架构问题,应该避免。如有必要,应将这两个类打包到一个模块中。这显然是一种倒退。我尝试让模块变得小而简单。

我可以用指向实际已经存在的公共基类 NetworkObject 的指针替换指向节点或边的指针。但这会破坏有价值的信息,并迫使我使用 static_cast 人为地将类型信息添加回来。

我的问题是:我错过了什么吗?有更容易的方法吗?

Gui*_*cot 8

我在这里看到一些误解。不完全错误,但也不完全正确。

但在 C++20 模块的新世界中,前向引用不再起作用。

这并不完全正确。您不能使用前向引用将某些内容声明为不同模块的一部分,但您当然可以在同一模块中执行此操作。

例如:

export module M;

export namespace n {
    struct B;

    struct A {
        B* b;
    };

    struct B {
        A* a;
    };
}
Run Code Online (Sandbox Code Playgroud)

然后你可以将它分成多个模块分区:

export module M;

export namespace n {
    struct B;

    struct A {
        B* b;
    };

    struct B {
        A* a;
    };
}
Run Code Online (Sandbox Code Playgroud)
export module M:a;

namespace n {
    struct B;
    export struct A {
        B* b;
    };
};
Run Code Online (Sandbox Code Playgroud)
export module M:b;

namespace n {
    struct A;
    export struct B {
        A* b;
    };
};
Run Code Online (Sandbox Code Playgroud)

其要点是,要定义的相互依赖的类型具有足够的耦合性,因此它们必须驻留在同一模块中。

另外,请注意,模块不一定像标头一样精细。过多地划分模块可能会损害编译时性能。例如,整个库可能只是一个大模块。标准库选择了这种方法并导出std模块中的所有内容,结果证明它比将标准库划分为许多较小的模块更快。

较小的模块并不像许多人想象的那么好。相关的事物和类应该打包在同一个模块中,如果该模块内的代码组织需要进一步拆分,则可以选择分区。

模块的数量及其名称是 API 的一部分。这意味着,如果您有太多细粒度的模块,只需移动代码就会导致重大更改。模块分区不是 API 的一部分,可以自由移动。

一个常见的说法是循环引用是一个架构问题,应该避免。如有必要,应将这两个类打包到一个模块中。这显然是一种倒退。我尝试让模块变得小而简单。

由于它们之间的循环,这些模块不会很小而且很基本。即你不能只使用一个模块而不使用另一个模块。如果实现驻留在另一个静态库中,您将需要链接到另一个模块。

这两个类描述了图的节点和边

我们有一个程序只能使用节点模块或边缘模块吗?几乎不。它们应该是graph模块的一部分。您可以有:edge:node分区,但在程序或程序的一部分中仅使用其中一个是没有意义的。

如果这是为了编译时间,那么今天已经证明,使用当前编译器技术制作更大的模块比较小的模块更快

将模块拆分为更小的模块的基本原理是,存在只想导入某些特定内容的用例。例如,std.freestanding仅包含标准库的独立部分,因此程序员不会意外使用他们不允许使用的部分。


当然,另一种方法是放弃所有模块保护措施并使用全局模块片段(GMF)。使用它允许模块与隐式全局模块进行交互。是的,使用它可以带来全球前瞻性声明带来的好处和后果。您将为 ODR 违规再次成为可能开辟道路,并且您的实体将不再是命名模块的一部分。它还允许用户使用您的实体,而无需导入声明所在的特定命名模块,绕过您通过模块名称向用户公开的 API。

您可以使用以下指令打开潘多拉魔盒extern "C++"

export module A;

export namespace n {
    extern "C++" {
        struct B;
        struct A {
            B* b;
        };
    }
}
Run Code Online (Sandbox Code Playgroud)
export module B;

export namespace n {
    extern "C++" {
        struct A;
        struct B {
            A* a;
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

实例