【问题标题】:Complex circular dependency复杂的循环依赖
【发布时间】:2011-03-19 17:17:58
【问题描述】:

解决C++中循环依赖的最佳实践是什么?

我可以使用前向声明,但随后出现pointer to incomplete class type is not allowed 错误。这是否意味着使用彼此指针的两个类不能相互依赖?

另外,我考虑过前向声明每个类,然后将解决方案的每个标题包含在main.cpp 中,所以它们都在一个地方。你会推荐它吗?

下面是整个项目的 sn-p,因此如果问题在我熟悉的示例中得到更好的解释,您可以参考它,但这只是理论上的。 谢谢

【问题讨论】:

    标签: c++ oop circular-dependency


    【解决方案1】:

    你只需要正确使用前向声明:

    1. 将所有代码放入 cpp 文件中
    2. 只将类声明放在头文件中
    3. 在头文件中:
      1. 如果只使用指针或引用,请使用前向声明。
      2. 否则你要包含头文件。 (不要添加不需要的包含)
    4. 在 cpp 文件中
      1. 包括您需要的所有头文件。

    注意:添加包含防护。

    如果没有实际的声明,很难真正做到这一点。该图很好,但没有足够的信息。一张图片可能值一千个单词,但一种精确定义的语言可以非常紧凑地传达更准确的信息(不像英语及其前后矛盾)。

    【讨论】:

    • 哦,你说得对,先生!感谢一百万防止我的解决方案因接口而变得臃肿。奇迹般有效。只是跟进:如果两个类在值上相互依赖,这个过程在第 3 步不会失败吗?
    • @Mikulas Dite:想想可能发生的情况。尝试在单个文件中执行此操作,您也会失败。这是一种递归依赖(您也可以使用一个类来实现) X 类有一个 X 类型的成员。或者 X 类有一个 Y 类型的成员,而 Y 类有一个 X 类型的成员。该语言不支持这种类型构造)。
    • 我想我之前应该考虑一下。你是对的,这样的结构并没有真正的意义。
    【解决方案2】:

    一个想法是引入接口并删除循环依赖。因此,您将拥有一个 Effect、Player 和 EffectContainer 所依赖的 IEffect。可能,如果 Player 依赖于 Effect 的某些行为,而 EffectContainer 依赖于一组不同的行为,我会考虑引入两个接口,有效地遵循Interface Segregation Principle。这也将与Dependency Inversion Principle 一起出现。

    【讨论】:

    • 谢谢,我没想到可以用这么干净的方式解决这个问题。
    • 我首先选择了这个答案,但正如@Martin 指出的那样,正确设置包含和前向声明也可以处理这个问题,而不会用接口使代码臃肿,我的投票是他的答案。
    【解决方案3】:

    通常这是通过让每个头文件在其#include 之前预先声明它需要的类来实现的。此外,不应将任何代码放入头文件中。所以你最终得到:

    class Effect;
    class Player;
    class GameStack;
    
    #include <vector>
    // more includes
    
    class EffectContainer { ... }
    

    以及每个地方的等价物。然后在您的 .cpp 文件中,您实际上是 #include 其他类的标题。如果您的对象对其他类的内存布局没有循环依赖,这将起作用。这意味着方法和成员只能通过引用或指针(但不能通过值)引用其他类。如果你有类似的东西,这可能会有点松鼠

     class EffectContainer {
       std::Vector<Effect> effects;
     }
    
     class Effect {
       boost::shared_ptr<EffectContainer> parent;
     }
    

    因为模板扩展有时需要完整类型,而不仅仅是预先声明的类型。许多库避免此问题的一种方法是使用指向私有 impl 模式(通常称为 PIMPL 模式)的指针,其中每个类都定义为:

    class FooImpl;
    class Foo {
      FooImpl* impl;
    }
    

    那么FooImpl 完全在.cpp 文件中定义,您的循环问题可以避免。这种设计也很有价值,因为它保持了库版本之间的二进制兼容性。它确实有点冗长,但没有人说C++ 是一种简洁的语言。

    【讨论】:

    • 仅供参考,因为我不相信Matt提到了这个名字,所以这通常是指一个PIMPL成语。您可以通过 google 搜索找到大量文章,这些文章将详细解释其工作原理。
    • 很好,我确实提供了扩展“指向私有实现的指针”,但忘记提及短名称。
    • 谢谢,但我现在会坚持使用接口。然而,这似乎非常相似。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-26
    • 1970-01-01
    • 1970-01-01
    • 2013-12-10
    相关资源
    最近更新 更多