我经常发现自己处于这样一种情况:由于一些糟糕的设计决策(由其他人做出:),我在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;
}
不幸的是,我不能评论geza的答案。
他不仅仅是说“把声明放到一个单独的头文件中”。他说,你必须将类定义头文件和内联函数定义分离到不同的头文件中,以允许“延迟依赖”。
但是他的插图不是很好。因为这两个类(A和B)只需要彼此的不完整类型(指针字段/参数)。
为了更好地理解它,想象类A有一个类型为B而不是B*的字段。此外,类A和类B想定义一个内联函数,参数类型为另一种:
这段简单的代码行不通:
// A.h
#pragme once
#include "B.h"
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
// B.h
#pragme once
class A;
class B{
A* b;
inline void Do(A a);
}
#include "A.h"
inline void B::Do(A a){
//do something with A
}
//main.cpp
#include "A.h"
#include "B.h"
这将导致以下代码:
//main.cpp
//#include "A.h"
class A;
class B{
A* b;
inline void Do(A a);
}
inline void B::Do(A a){
//do something with A
}
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
//#include "B.h"
这段代码不能编译,因为B::Do需要后面定义的a的完整类型。
为了确保它能编译源代码,应该是这样的:
//main.cpp
class A;
class B{
A* b;
inline void Do(A a);
}
class A{
B b;
inline void Do(B b);
}
inline void B::Do(A a){
//do something with A
}
inline void A::Do(B b){
//do something with B
}
对于需要定义内联函数的每个类,使用这两个头文件是完全可能的。
唯一的问题是循环类不能只包含“公共标头”。
为了解决这个问题,我想建议一个预处理器扩展:#pragma process_pending_includes
这个指令应该延迟当前文件的处理,并完成所有挂起的include。
维基百科上的简单例子对我很有用。
(你可以在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;
}
不幸的是,我不能评论geza的答案。
他不仅仅是说“把声明放到一个单独的头文件中”。他说,你必须将类定义头文件和内联函数定义分离到不同的头文件中,以允许“延迟依赖”。
但是他的插图不是很好。因为这两个类(A和B)只需要彼此的不完整类型(指针字段/参数)。
为了更好地理解它,想象类A有一个类型为B而不是B*的字段。此外,类A和类B想定义一个内联函数,参数类型为另一种:
这段简单的代码行不通:
// A.h
#pragme once
#include "B.h"
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
// B.h
#pragme once
class A;
class B{
A* b;
inline void Do(A a);
}
#include "A.h"
inline void B::Do(A a){
//do something with A
}
//main.cpp
#include "A.h"
#include "B.h"
这将导致以下代码:
//main.cpp
//#include "A.h"
class A;
class B{
A* b;
inline void Do(A a);
}
inline void B::Do(A a){
//do something with A
}
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
//#include "B.h"
这段代码不能编译,因为B::Do需要后面定义的a的完整类型。
为了确保它能编译源代码,应该是这样的:
//main.cpp
class A;
class B{
A* b;
inline void Do(A a);
}
class A{
B b;
inline void Do(B b);
}
inline void B::Do(A a){
//do something with A
}
inline void A::Do(B b){
//do something with B
}
对于需要定义内联函数的每个类,使用这两个头文件是完全可能的。
唯一的问题是循环类不能只包含“公共标头”。
为了解决这个问题,我想建议一个预处理器扩展:#pragma process_pending_includes
这个指令应该延迟当前文件的处理,并完成所有挂起的include。
不幸的是,之前所有的答案都遗漏了一些细节。正确的解有点麻烦,但这是唯一正确的方法。而且它易于扩展,也能处理更复杂的依赖关系。
以下是如何做到这一点,准确地保留所有细节和可用性:
解决方案与最初的计划完全相同
内联函数仍然是内联的
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。