C++ 中的循环依赖

Posted

技术标签:

【中文标题】C++ 中的循环依赖【英文标题】:Circular Dependency in C++ 【发布时间】:2011-04-30 10:46:09 【问题描述】:

事实:

我有两个主要类别:经理和专家。 有几种不同类型的专家。 专家通常需要其他专家的帮助才能完成工作。 经理认识所有的专家,最初每个专家只认识他们的经理。 (这就是问题所在。) 在运行时,管理器创建并存储专家列表。然后 Manager 遍历列表并要求每个专家进行初始化。在初始化期间,每个专家都要求经理向他们提供满足某些描述的其他专家。完成后,经理将进入一个循环,在此期间专家被依次要求执行他们的专业任务。

对我来说,这似乎是一个不错的模式,但由于经理有专家列表,而专家有经理,我遇到了循环依赖问题。

在这种情况下,我应该以某种方式向前声明一个类的存在吗? (如果是这样,如何?)或者我应该使用一些设计模式来解决这个问题? (如果是这样呢?)另外......我虽然模式本身很不错。所以我不介意有人帮助我理解为什么这是一件坏事。

【问题讨论】:

您能否向我们展示您所拥有的样本以及您遇到的具体问题? 我最近多次看到这个非常相似的问题 - 这是最近的一个***.com/questions/4016471/… @Greg - 问题相似,但我不仅对解决循环依赖感兴趣,而且对了解我使用的模式是否因某种原因存在缺陷感兴趣。 您的情况在设计模式中很常见,请查看观察者设计模式 UML。你需要前向声明,所以也要写... 【参考方案1】:

这是个人喜好问题,但即使没有循环依赖,前向声明通常也是包含在头文件中的一个很好的替代方案。 (我不想在这个地方对此进行讨论。)所以,这里有一个关于如何为您的问题应用前向声明的示例:

在 Manager.h 中:

// Forward declaration:
class Specialist;

// Class declaration:
class Manager

    // Manager declarations go here.
    // Only pointers or references to
    // the Specialist class are used.
;

在 Manager.cpp 中:

#include "Manager.h"

#include "Specialist.h"

// Manager definitions/implementations
// using the Specialist class go here.
// Full Specialist functionality can be used.

在 Specialist.h 中:

// Forward declaration:
class Manager;

// Class declaration:
class Specialist

    // Specialist declarations go here.
    // Only pointers or references to
    // the Manager class are used.
;

在 Specialist.cpp 中:

#include "Specialist.h"

#include "Manager.h"

// Specialist definitions/implementations
// using the Manager class go here.
// Full Manager functionality can be used.

【讨论】:

感谢您提供明确的示例,这是迄今为止唯一有用的答案! 但是不包含Manager.h的情况下,Manager.cpp中的功能如何实现呢? Manager.h 当然也包含在Manager.cpp 中。我刻意避免解释这个含义。如果不清楚或可能导致混淆,我现在再次编辑了我的答案。【参考方案2】:

当其他人都在回答核心问题时,我想我会指出这一点。

在运行时,管理器创建并存储专家列表。然后 Manager 遍历列表并要求每个专家进行初始化。在初始化期间,每个专家都要求经理向他们提供满足某些描述的其他专家。完成后,经理将进入一个循环,在此期间专家被依次要求执行他们的专业任务。

我只想指出,这需要一个两步过程。如果经理到目前为止只知道一位专家,那么经理如何告诉专家 1 任务 B 中存在哪些专家?所以你需要:

1) 经理查看专家名单并要求他们表明身份。

2) 经理查看专家列表并询问他们需要访问哪些专业,告诉他们谁可以满足他们的要求。

3) 经理查看专家列表并告诉他们执行他们的操作。

【讨论】:

【参考方案3】:

在这两种情况下,前向声明另一个类:

Manager.h

class Specialist;

class Manager

    std::list<Specialist*> m_specialists;
;

Specialist.h

class Manager;

class Specialist

    Manager* m_myManager;
;

只有当你需要在该类中使用成员函数或变量,或者需要将类用作值类型等时,才需要为类引入头文件。当你只需要一个指针时或引用类,前向声明就足够了。

请注意,前向声明不仅仅用于解决循环依赖。您应该尽可能使用前向声明。如果可行的话,它们总是比包含额外的头文件更可取。

【讨论】:

“它们总是更可取”我强烈不同意,并认为它们很少在可以避免的地方更可取。大量使用前向声明会使代码更难理解,因为追踪依赖关系变得更加困难。此外,当使用积极缓存文件并支持预编译头文件的现代编译器时,通常几乎没有性能提升。 [在一个无关的注释上,你为什么建议std::list?] 追踪哪些依赖项?如果前向声明有效,则不再存在依赖关系。此外,在任何中等体面的 IDE 中跟踪类跟踪和标题跟踪一样容易。至于使用std::list,他在 OP 中说 Manager 存储了专家列表,所以我决定从字面上解释。我自己可能会使用vector,但这当然取决于特定的用例。 谢谢!我害怕我进入了某种非直觉的反模式。很高兴看到每个人都认为这是相当标准的票价。【参考方案4】:

这是正常的东西。你只需要

class Manager;

在专家标题中和

class Specialist; 

在经理标题中

如果您使用 shared_ptrs,您可能会发现 shared_from_this 很有用。 (不是为了循环,而是因为听起来你无论如何都需要它)

【讨论】:

【参考方案5】:

一种选择是按照您的建议转发声明其中一个人:

struct specialist;

struct manager

    std::vector<std::shared_ptr<specialist> > subordinates_;
;

struct specialist

    std::weak_ptr<manager> boss_;
;

但是,如果您最终拥有更多的树形结构(您有多层管理,person 基类也可以工作:

struct person

    virtual ~person()  
    std::weak_ptr<person> boss_;
    std::vector<std::shared_ptr<person> > subordinates_;
;

然后,您可以为层次结构中不同类型的人派生特定类。您是否需要这取决于您打算如何使用这些类。

如果您的实现不支持std::shared_ptr,它可能支持std::tr1::shared_ptr,或者您可以使用boost::shared_ptr

【讨论】:

在这个模型中,你必须确保所有manager 指针都包裹在shared_ptr 中吗?否则无法验证weak_ptr 是否存在?对此感到好奇,因为它对我来说很重要。 @Steve:是的。如果层次结构中只有一种类类型(例如我的第二个示例中的person),则会容易得多。

以上是关于C++ 中的循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

C++循环依赖

C++ 循环依赖,未声明的标识符

不同命名空间内的类的循环依赖 C++

解决由于类之间的循环依赖而导致的构建错误

解决由于类之间的循环依赖而导致的构建错误

解决由于类之间的循环依赖而导致的构建错误