【问题标题】:Avoiding Circular Dependencies of header files [duplicate]避免头文件的循环依赖
【发布时间】:2011-06-16 13:06:08
【问题描述】:

您对如何避免头文件的循环依赖有什么好的建议吗?

当然,从一开始,我就尝试将项目设计得尽可能透明。然而,随着越来越多的特性和类被添加,项目变得越来越不透明,循环依赖开始出现。

是否有任何通用的、经过验证的和有效的规则?谢谢。

【问题讨论】:

    标签: c++ software-design architecture


    【解决方案1】:

    如果你有循环依赖,那么你做错了什么。

    例如:

    foo.h
    -----
    class foo {
    public:
       bar b;
    };
    
    bar.h
    -----
    class bar {
    public:
       foo f;
    };
    

    你可能想要的是非法的:

    foo.h
    -----
    class bar; // forward declaration
    class foo {
       ...
       bar *b;
       ...
    };
    
    bar.h
    -----
    class foo; // forward declaration
    class bar {
       ...
       foo *f;
       ...
    };
    

    这没关系。

    一般规则:

    1. 确保每个标题都可以单独包含。
    2. 如果您可以使用前向声明,请使用它们!

    【讨论】:

    • +1 您好 Artyom,感谢您的回复。更频繁地使用前向声明可能会有所帮助。
    • @Artyom:如果指针是为了拥有资源,我建议使用scoped_ptrunique_ptr。如果指针只是对对象的引用,那么可能有必要使用观察者模式,以便在所引用的对象被销毁时“取消设置”。
    • @Matthieu M. 当然,(或auto_ptr,当您不想依赖boostC++0x 时更好)。但我更想展示一般的想法而不是实时代码。如果 foo 有前向声明,它甚至可能是 std::vector<foo>
    • @Artyom:不,不要使用auto_ptr,这样更糟。如果需要,可以从 boost 中删除 scoped_ptr 的代码,但是 auto_ptr 带来了太多的惊喜(在复制/分配时)。
    • 但是如果我们使用 *b 指针的一些方法呢?然后我们不能转发包含它。那该怎么办?这里表明我们可以内联这些函数cplusplus.com/forum/articles/10627,但它看起来不是一个好的通用方法
    【解决方案2】:
    • 尽可能使用前向声明。
    • 如果只有 cpp 文件需要,则将任何包含的头文件从头文件中移出并放入相应的 cpp 文件中。执行此操作的最简单方法是使#include "myclass.h" 成为myclass.cpp 中的第一个包含。
    • 在不同类之间的交互点引入接口有助于减少依赖关系。

    【讨论】:

    • +1 您好乔恩,感谢您的回复。上面已经提到了您的一些建议,但是始终将头文件#include 到 .cpp 文件而不是 .h 文件的建议是新的并且很有帮助。
    • 我认为这个答案更好地解决了关于如何避免循环依赖的编译错误同时避免你做错了什么的口头禅,因为你必须处理循环依赖。如果您正在使用 GoF 设计模式和复杂性,您将在某些时候产生循环依赖。最好的建议不仅仅是前向声明(过度简化了解决方案),而是要点 #2。
    • 第二个建议是我想要的
    【解决方案3】:

    我遵循的一些避免循环依赖的最佳实践是,

    1. 坚持 OOAD 原则。不要包含头文件,除非包含的类与当前类有组合关系。请改用前向声明。
    2. 设计抽象类作为两个类的接口。通过该接口进行类的交互。

    【讨论】:

    • +1 嘿 Arun,尤其是使用抽象/接口类的第二条建议很有帮助。我会试一试。谢谢。
    【解决方案4】:

    一般的做法是将共性分解到第三个头文件中,然后由两个原始头文件引用。

    另见Circular Dependency Best Practice

    【讨论】:

    • +1 嗨,Ed,这是另一个非常好的建议。谢谢。
    • 我检查了您提供的链接,它显示了重新设计类以避免循环依赖的好例子。
    【解决方案5】:

    取决于您的预处理器功能:

    #pragma once
    

    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    your header file
    #endif
    

    如果你觉得设计头文件很无聊,也许你可能会对来自 Hwaci(SQLite 和化石 DVCS 的设计者)的 makeheaders 感兴趣。

    【讨论】:

    • 这不是为了避免循环依赖,而是为了避免“重新定义符号”错误。尽管如此,这是一种标准的、绝对需要的做法。
    • 您好 Benoid,是的,我必须同意 Peter Torok。这在每本教科书和必须使用的练习中都有解释。非常感谢您的回复。
    【解决方案6】:

    您的目标是 layered approach。您可以定义模块可以依赖于较低层模块的层,但应使用 observers 完成相反的操作。现在你仍然可以定义你的层应该有多细以及你是否接受层内的循环依赖,但在这种情况下我会使用this

    【讨论】:

    • +1 你好 Stefaanv,分层方法对我来说很新,看起来需要大量准备和重新设计。这是非常宝贵的建议。谢谢。
    • 分层方法是个好主意,特别是因为它不是 C++ 特定的,因此在很多情况下都很有价值:)
    【解决方案7】:

    一般来说,头文件应该向前声明,而不是尽可能包含其他头文件。

    还要确保每个标题都坚持一个类。

    那么你几乎肯定不会出错。

    最糟糕的耦合通常来自于臃肿的模板代码。因为你必须在头文件中包含定义,这通常会导致必须包含各种头文件,然后使用模板的类包含模板头,包括大量其他内容。

    出于这个原因,我通常会说:小心使用模板!理想情况下,模板不应该在其实现代码中包含任何内容。

    【讨论】:

    • +1 嗨 CashCow,老实说,我并没有太注意前向声明。相反,我使用#include。非常感谢您的回答。
    【解决方案8】:

    Altough Artyom 提供了最佳答案,本教程也很棒,并提供了一些扩展 http://www.cplusplus.com/forum/articles/10627/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-06
      • 1970-01-01
      • 2012-02-15
      • 2012-08-10
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      相关资源
      最近更新 更多