C++头文件包含的相关问题

前两天,室友测试C++的循环包含(cyclic include)和友元函数,但却总是报错。我们一起探讨并查询了各类资料后,终于搞清楚了原因。

问题

我们有AB两个类,分别在不同的头文件中,两个头文件互相包含(但由于有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&);

解决方法

经过分析与误打误撞的尝试,我们发现有以下两种消除错误的方法:

  1. 删除B.h中的#include "A.h"语句
  2. 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_HA.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.hB.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 协议 ,转载请注明出处!