我经常发现自己处于这样一种情况:由于一些糟糕的设计决策(由其他人做出:),我在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.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;
}