C++如何解决头文件循环嵌套问题
一个项目中,有时我们会遇到这样的情况,两个类之间需要相互通讯,它们相互之间都要调用对方的函数。比如下面的例子中,有Parent
和Child
两个类,它们都含有一个名为update
的方法。Parent
当中含有一个Child
的实例,当Parent
的update
执行时,Child
实例的update
也需要执行,并且Child
实例执行update
的过程中如果触发了某种条件会执行Parent
中的某个函数foo
。这样,两个类你中有我,我中有你,稍有不慎可能就写成了下面这种错误的形式:
//错误示范
//Parent.h
#pragma once
#include "Child.h"
class Parent
{
public:
Parent();
void update();
void foo();
private:
Child* mChild;
};
//Parent.cpp
#include "Parent.h"
Parent::Parent() : mChild(nullptr)
{
mChild = new Child();
}
void Parent::foo()
{
//do something
}
void Parent::update()
{
mChild->update(this);
}
//Child.h
#pragma once
#include "Parent.h"
class Child
{
public:
void update(Parent*);
};
//Child.cpp
#include "Child.h"
void Child::update(Parent* parent)
{
//do something
if (/*some condition triggered*/)
{
parent->foo();
}
}
写成这种形式,代码的文本编辑器一般是不会认为它有问题的,从逻辑上考虑似乎也没有什么错。但是,编译的时候会报错,我们来考虑一下为什么。假设我们在main.cpp
当中包含的是Parent.h
头文件,那么在解析代码时,先将Parent.h
中的内容展开;Parent.h
的开头包含了Child.h
,再将Child.h
在Parent.h
的开头展开;Child.h
的开头虽然又包含了Parent.h
,但是由于此时Parent.h
已经被包含过一次,在#pragma once
指令或者头文件宏的约束下会避免将Parent.h
再次展开,最终呈现的样子大概是这样的:
class Child
{
public:
void update(Parent*);
};
class Parent
{
public:
Parent();
void update();
void foo();
private:
Child* mChild;
};
很明显,当编译器自上而下编译时,碰到void update(Parent*)
一句时,由于Parent
的声明在后面,编译器找不到Parent
的相关声明,所以会报错。
其实,解决的方法也很简单,就是把头文件的include
指令改成一个预先的声明class XXX;
,并将include
指令放在cpp
文件里,正确的代码如下:
//正确示范
//Parent.h
#pragma once
class Child; //这里发生了改变
class Parent
{
public:
Parent();
void update();
void foo();
private:
Child* mChild;
};
//Parent.cpp
#include "Parent.h"
#include "Child.h" //将头文件的include放在cpp文件里
Parent::Parent() : mChild(nullptr)
{
mChild = new Child();
}
void Parent::foo()
{
//do something
}
void Parent::update()
{
mChild->update(this);
}
//Child.h
#pragma once
class Parent; //这里发生了改变
class Child
{
public:
void update(Parent*);
};
//Child.cpp
#include "Child.h"
#include "Parent.h" //将头文件的include放在cpp文件里
void Child::update(Parent* parent)
{
//do something
if (/*some condition triggered*/)
{
parent->foo();
}
}
为了避免这种头文件循环嵌套的问题出现,我们应当尽量避免在头文件中引入其他头文件,改成在对应的cpp
文件中引入,如果头文件中引用了其他类的对象或者其指针,我们可以在头文件开头将对应类预先声明。但是,假如头文件中含有某个类具体的成员变量或者函数,就不能采用预先声明的方法了。