C++头文件包含的相关问题
前两天,室友测试C++的循环包含(cyclic include
)和友元函数,但却总是报错。我们一起探讨并查询了各类资料后,终于搞清楚了原因。
问题
我们有A
和B
两个类,分别在不同的头文件中,两个头文件互相包含(但由于有include guard
并不会出现循环包含问题)。A
类中有一个B
类友元函数,一切都是运行正确的,直到我们在B.cpp
中加入了B
类该函数的定义。
A.h
#ifndef A_H
#define A_H
#include "B.h"
class A {
friend void B::fun(A&);
};
#endif
B.h
#ifndef B_H
#define B_H
#include "A.h"
class A;
class B {
public:
void fun(A&);
};
#endif
B.cpp
#include "B.h"
void B::fun(A& a) {
}
main.cpp
#include "A.h"
#include "B.h"
int main() {
}
运行命令:
g++ -g -o test main.cpp B.cpp
报错信息:
A.h:7:17: error: 'B' has not been declared
friend void B::fun(A&);
解决方法
经过分析与误打误撞的尝试,我们发现有以下两种消除错误的方法:
- 删除
B.h
中的#include "A.h"
语句 - 将
B.cpp
中的#include "B.h"
该为#include "A.h"
错误分析
对于头文件包含产生的问题,最好的方法或许就是自己去扮演预处理器的角色。
C++中,.cpp
文件是一个编译单元,因此我们这里来一一分析.cpp
文件。
main.cpp
main.cpp
中先包含了A.h
,这时因为A_H
还未定义,则A.h
中就会定义A_H
防止后续的重复包含。A.h
又包含了B.h
,因为B_H
还未定义,则B.h
中就会定义B_H
。此时,B.h
又尝试包含A.h
,但由于A_H
已被定义,返回的是空文本。最终这些内容都被拷贝到main.cpp
中,大致如下:
#ifndef A_H
#define A_H
#ifndef B_H
#define B_H
class A;
class B {
public:
void fun(A&);
};
#endif
class A {
friend void B::fun(A&);
};
#endif
int main() {
}
由于B类中的fun函数只是需要A类的引用,而不是A的实例本身或A的成员变量,所以一个前置声明class A;
是足够的,并不会报错。
而A类中需要用到B类的成员函数,因此需要有B类的前置声明和该成员函数的前置声明,预处理后的文件中确实做到了这一点,所以也不会报错。
关于前置声明,不了解的读者请仔细阅读When can I use a forward declaration?。
B.cpp
由于每个.cpp
文件是独立的编译单元,因此编译完main.cpp
后,将编译B.cpp
,而且之前处理的宏定义(如#ifndef,#define等)都会被遗忘[1]。
B.cpp
中包含了 B.h
,由于没有定义B_H
,则会定义B_H
,然后B.h
包含A.h
,定义A_H
,A.h
中还尝试包含B.h
,但由于B_H
已被定义,返回的是空文本。最终内容被拷贝到B.cpp
中,大致如下:
#ifndef B_H
#define B_H
#ifndef A_H
#define A_H
class A {
friend void B::fun(A&);
};
#endif
class A;
class B {
public:
void fun(A&);
};
#endif
可以发现,A类中用到了B的成员函数,但B的定义却在后面,所以找不到B类及其成员函数的定义,报错。
解决方法分析
方法一
由于B.h
中我们只用了A类的引用,所以其实一个前向声明就足够了。加上了#include "A.h"
,反而引入了A类中B类成员函数找不到声明的问题。因此,删掉#include "A.h"
即可,这是比较好的解决方法。
方法二
由于出错的原因就是B.cpp
中包含B.h
,B.h
又包含A.h
,引入了A类中B类成员函数找不到声明的问题,因此我们可以直接在B.cpp
中包含A.h
,这样B类的定义就会在A类前面,不会报错。但是一般而言实现文件应该是要包含对应的头文件的,这样消除错误会带来耦合。
拓展思考
由于C++是分离编译的,则main.cpp
->main.obj
,而B.cpp
->B.obj
,按理说这两个目标文件中都应该包含着class B
的定义,那为什么没有重复定义的错误呢?
根据查询的资料,因为C++是允许类存在重复定义的,但是所定义的类必须完全一致[2]。具体的原因我也还不清楚,应该是关于设计、链接等方面的原因,等我以后进行了更深层次的学习后,再来补上。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!