我经常发现自己处于这样一种情况:由于一些糟糕的设计决策(由其他人做出:),我在c++项目中面临多个编译/链接器错误,这导致了不同头文件中c++类之间的循环依赖(也可能发生在同一个文件中)。但幸运的是,这种情况发生的次数并不多,所以当下次再次发生这种情况时,我还能记住解决这个问题的方法。

因此,为了便于以后回忆,我将发布一个有代表性的问题和解决方案。更好的解决方案当然是受欢迎的。


A.h B类; A类 { int _val; B * _b; 公众: (int val) : _val (val) { } SetB(B * B) { _b = b; _b - >打印();//编译错误:C2027:使用未定义的类型“B” } 无效的Print () { cout < <“类型:val = " < < _val < < endl; } };


B.h # include“A.h” B类 { 双_val; * _a; 公众: B(双val) : _val (val) { } SetA(A * A) { _a = a; _a - >打印(); } 无效的Print () { cout < <“B型:val = " < < _val < < endl; } };


main.cpp # include“B.h” # include < iostream > Int main(Int argc, char* argv[]) { 一个(10); B B (3.14); a.Print (); a.SetB(乙); b.Print (); b.SetA(和); 返回0; }


当前回答

不幸的是,之前所有的答案都遗漏了一些细节。正确的解有点麻烦,但这是唯一正确的方法。而且它易于扩展,也能处理更复杂的依赖关系。

以下是如何做到这一点,准确地保留所有细节和可用性:

解决方案与最初的计划完全相同 内联函数仍然是内联的 A和B的用户可以任意顺序使用A.h和B.h

创建两个文件,A_def.h, B_def.h。这些将只包含A和B的定义:

// A_def.h
#ifndef A_DEF_H
#define A_DEF_H

class B;
class A
{
    int _val;
    B *_b;

public:
    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

// B_def.h
#ifndef B_DEF_H
#define B_DEF_H

class A;
class B
{
    double _val;
    A* _a;

public:
    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

然后,A.h和B.h会包含这个

// A.h
#ifndef A_H
#define A_H

#include "A_def.h"
#include "B_def.h"

inline A::A(int val) :_val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif

// B.h
#ifndef B_H
#define B_H

#include "A_def.h"
#include "B_def.h"

inline B::B(double val) :_val(val)
{
}

inline void B::SetA(A *a)
{
    _a = a;
    _a->Print();
}

inline void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

#endif

注意,A_def.h和B_def.h是“私有”头文件,A和B的用户不应该使用它们。公共头文件是A.h和B.h。

其他回答

思考这个问题的方法是“像编译器一样思考”。

假设您正在编写一个编译器。你会看到这样的代码。

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

当你编译。cc文件时(记住。cc而不是。h是编译的单位),你需要为对象a分配空间,那么,那么,有多少空间呢?足够储存B了!那么B的大小是多少呢?足够储存A!哦。

显然你必须打破一个循环引用。

你可以通过允许编译器保留尽可能多的空间来打破它——例如,指针和引用将始终是32或64位(取决于体系结构),所以如果你用指针或引用替换(任何一个),事情就会很好。我们在A中替换:

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

现在情况好多了。有点。Main()仍然说:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include,对于所有的范围和目的(如果你取出预处理器)只是复制文件到.cc。所以实际上,。cc看起来像:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

你可以看到为什么编译器不能处理这个——它不知道B是什么——它以前甚至从来没有见过这个符号。

因此,让我们告诉编译器关于b的信息。这被称为前向声明,并在本回答中进一步讨论。

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

这个作品。这并不好。但是在这一点上,您应该已经理解了循环引用问题,以及我们如何“修复”它,尽管修复是糟糕的。

这个修复很糟糕的原因是,下一个要#include“A.h”的人在使用它之前必须声明B,并且会得到一个可怕的#include错误。让我们把声明移到A.h本身。

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

在B.h中,此时你可以直接包含“A.h”。

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.

需要记住的事情:

如果类A有类B的对象作为成员,这将不起作用,反之亦然。 向前申报是一种方式。 声明的顺序很重要(这就是为什么要移出定义)。 如果两个类都调用另一个类的函数,则必须将定义移出。

阅读常见问题:

如何创建两个相互了解的类? 对成员对象使用前向声明时需要特别注意什么? 前向声明与内联函数一起使用时需要特别注意什么?

不幸的是,之前所有的答案都遗漏了一些细节。正确的解有点麻烦,但这是唯一正确的方法。而且它易于扩展,也能处理更复杂的依赖关系。

以下是如何做到这一点,准确地保留所有细节和可用性:

解决方案与最初的计划完全相同 内联函数仍然是内联的 A和B的用户可以任意顺序使用A.h和B.h

创建两个文件,A_def.h, B_def.h。这些将只包含A和B的定义:

// A_def.h
#ifndef A_DEF_H
#define A_DEF_H

class B;
class A
{
    int _val;
    B *_b;

public:
    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

// B_def.h
#ifndef B_DEF_H
#define B_DEF_H

class A;
class B
{
    double _val;
    A* _a;

public:
    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

然后,A.h和B.h会包含这个

// A.h
#ifndef A_H
#define A_H

#include "A_def.h"
#include "B_def.h"

inline A::A(int val) :_val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif

// B.h
#ifndef B_H
#define B_H

#include "A_def.h"
#include "B_def.h"

inline B::B(double val) :_val(val)
{
}

inline void B::SetA(A *a)
{
    _a = a;
    _a->Print();
}

inline void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

#endif

注意,A_def.h和B_def.h是“私有”头文件,A和B的用户不应该使用它们。公共头文件是A.h和B.h。

维基百科上的简单例子对我很有用。 (你可以在http://en.wikipedia.org/wiki/Circular_dependency#Example_of_circular_dependencies_in_C.2B.2B上阅读完整的描述)

文件“a.h”:

#ifndef A_H
#define A_H

class B;    //forward declaration

class A {
public:
    B* b;
};
#endif //A_H

文件“b.h”:

#ifndef B_H
#define B_H

class A;    //forward declaration

class B {
public:
    A* a;
};
#endif //B_H

文件“main.cpp”:

#include "a.h"
#include "b.h"

int main() {
    A a;
    B b;
    a.b = &b;
    b.a = &a;
}

我曾经解决过这类问题,方法是将所有内联移到类定义之后,并将其他类的#include放在头文件中的内联之前。这样可以确保在解析内联之前设置所有定义+内联。

这样做可以使两个(或多个)头文件中仍然有大量内联。但有必要配备保安。

像这样

// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
    int _val;
    B *_b;
public:
    A(int val);
    void SetB(B *b);
    void Print();
};

// Including class B for inline usage here 
#include "B.h"

inline A::A(int val) : _val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif /* __A_H__ */

...在B.h中做同样的事情