今天下午遇到一个头文件相互包括而导致的编译问题,花了我不少时间去调试没找到问题。最后晚上跟师兄讨论不少时间,突然有所顿悟!
问题重现
我把问题脱离于项目简单描写叙述一下:我写了一个函数 bool func(ClassA* CA)
须要加到项目中,我就把这个函数的声明放到 head1.h
中,函数參数类型 ClassA 定义在还有一个头文件 head2.h
中,因此我须要在 head1.h
中包括 head2.h
;而 head2.h
中之前又包括了 head1.h
。这样就构成了一种头文件相互包括的场景。再加上一些其他的声明与定义,就构成了这种一个文件结构:
- head1.h
#ifndef __HEAD_1_H__ #define __HEAD_1_H__ #include "head2.h" #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h #endif
- head2.h
#ifndef __HEAD_2_H__ #define __HEAD_2_H__ #include "head1.h" class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; #endif
那么,如今另有两个源文件
- source1.cpp
#include "head1.h" //... some source code
- source2.cpp
#include "head2.h" //... some source code
整个项目会分别编译这两个源文件,编译完之后会报错。大致意思是 ClassA 和 VAR_MACRO 未定义,那么问题就比較奇怪了。每一个头文件都分别引用了还有一个头文件,为什么会出现未定义呢?
问题分析
我们都知道 C/C++ 中头文件開始习惯使用 #ifndef ... #define ... #endif
这种一组预处理标识符来防止反复包括。比如上面的问题中我也使用了,假设不使用的话,两个头文件相互包括。就出现递归包括。这个非常好理解就不多叙。
回到问题本身,我在微博上贴出了这个问题。有人说在 head1.h
的函数前加上 ClassA
的前置声明,至于为什么要加,预计非常多人都没理解...
A;
对于 source1.cpp
,它包括了头文件 head1.h
。那么在编译之前,在 source1.cpp
中展开 head1.h
,而 head1.h
又包括了 head2.h
,
那么也展开它,这时 source1.cpp
就变成类似以下这样:
class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h //... source1.cpp source code
看到没,这地方 func 函数之前有 ClassA 类型的定义,根本不是必需像有些人说的那样加上 ClassA CA;
这种前置声明。
我们再展开 source2.cpp
看看:
#define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; //... source2.cpp source code
这时问题就非常清楚了。func 函数声明之前并没有发现 ClassA 类型定义,该定义在函数声明的后面,这时候假设能在head1.h
的函数声明之前加上 ClassA
的前置声明。就不会在编译的时候报找不到 ClassA 的定义的错误了。
CA;
再回到 source1.cpp
展开的源代码看看。是不是一下子明确了为什么报找不到 VAR_MACRO 的定义的错误了?改动方法也简单,把宏定义拉到 #include
语句之前就 OK 了。
"head2.h"
问题反思
如今回头想想这个问题。事实上是个非常easy的头文件包括的问题。假设了解一些编译器的预编译过程,错误原理也非常easy。
但为什么我卡在这个问题非常长时间,原因有下面几点:
- 问题出如今一个项目的编译过程中,未能准确地定位问题的解决办法
- 出现故障。没有静下来心来理清问题。C/C++ 编译基础知识点尽管知道。但未能第一时间运用到该问题的分析上
- 师兄说加上前置类的声明。确实攻克了类类型找不到的错误。尽管有问题解决方法。但没有真正理解这样的做法的道理,以至于宏定义找不到的错误还是不知怎样去解决(事实上本质是一样的)
总的来说。遇到问题不要慌,保持大脑清醒,把加一行或减一行代码期望就能碰运气编译通过的时间拿来分析问题更有效,解决这个问题之后一定确定自己是否知其然亦知其所以然!
參考资料
Google找了一圈。没找到有价值的资料....