
问题
下面是几个编译错误:
missing type specifier - int assumed.
unknown override specifier.
'CTestA' does not name a type
我想不少人会遇到上面的编译错误,这很可能是交叉引用所造成的。
虽然C++交叉引用并不是一个很常见的问题,而且应该尽量避免交叉引用。但是如果迫不得已非要采取交叉引用的话,那有什么好的解决方法吗?答案是肯定的。
解决方法
下面我们通过一个简单的例子来辅助说明。下面是某个工程下的几个文件:
- TestB.h
#include "CTestB.h"
class CTestB
{
private:
CTestA m_a;
public:
CTestB() {}
virtual ~CTestB() {}
};
- TestA.h
#include "CTestA.h"
class CTestA
{
public:
CTestB m_b;
public:
CTestA() {}
virtual ~CTestA() {}
};
- Test.cpp
#include <iostream>
#include "CTestA.h"
#include "CTestB.h"
int main()
{
CTestA A;
return 0;
}
如果我们使用GNU GCC编译器,那么编译结果十分的恐怖,不仅编译时间比较长,而且产生的错误也会很多,你可能会得到下面的编译日志:
mingw32-g++.exe -Wall -fexceptions -g -Iinclude -c
E:\blog_test\main.cpp -o obj\Debug\main.o
In file included from include/CTestB.h:4:0,
from include/CTestA.h:4,
from include/CTestB.h:4,
from include/CTestA.h:4,
from include/CTestB.h:4,
from include/CTestA.h:4,
...
from include/CTestA.h:4,
from include/CTestB.h:4,
from include/CTestA.h:4,
from E:\blog_test\main.cpp:2:
include/CTestB.h:6:7: error: redefinition of 'class CTestB'
以及下面的编译信息:
include\CTestA.h|4|error: #include nested too deeply
include\CTestB.h|4|error: #include nested too deeply
include\CTestA.h|9|error: 'CTestB' does not name a type
include\CTestB.h|6|error: previous definition of 'class CTestB'
include\CTestA.h|6|error: redefinition of 'class CTestA'
...
include\CTestA.h|6|error: previous definition of 'class CTestA'
include\CTestB.h|6|error: redefinition of 'class CTestB'
include\CTestB.h|6|error: previous definition of 'class CTestB'
我们不难发现,上面的编译错误的很可能就是上述的两个头文件互相引用对方所造成的,即所谓的交叉引用。其实原理也很好理解,我们可以站在编译器编译器角度(think like a compiler)想一想:
当你编译编译源文件时,你需要为对象A
分配空间,因为A
中有成员变量m_b
(CTestB
类型),所以给A分配的空间必须足够存放m_b
。那么此时也必须为m_b
分配足够存放CTestA
大小的空间...如此循环下去,你会发现编译进入了一个死锁。于是就出现了上面我们看到的编译结果。
NOTE:编译信息取决于你使用的编译器,使用不同的编译器可能会得到不同的编译结果。
宏保护(Include guard)
为了防止编译过程进入死锁,我们可以在头文件中加入一些特殊的宏:
#ifndef CTESTB_H
#define CTESTB_H
...
#endif
或者使用
#pragma once
这两种宏都可以确保所在文件文件在一次单独编译中只被包含一次。这两者的优缺点比较可以在这里找到。即使在上面的文件中加入宏保护,也无法完全解决问题,程序有产生了 新的编译错误:
'CTestA' does not name a type
前置声明(Forward declaration)
要解决上述编译错误,可以采取前置声明(Forward declaration),即在定义CTestA之前,先声明类CTestB;类CTestB同理。同时让我们来看看头文件TestA.h的改变:
- TestA.h
#ifndef CTESTA_H
#define CTESTA_H
#include "CTestB.h"
class CTestB;
class CTestA
{
public:
CTestB *m_b;
public:
CTestA() {}
virtual ~CTestA() {}
};
#endif // CTESTA_H
这里我们必须要注意一点,类A中的成员变量必须是指针或引用类型。有一篇博文中很形象的讲解了前置声明,有兴趣的朋友可以参考这里。
总结
言多必失,这里我只用一句话作总结:在程序中我们应该尽量避免使用C++交叉引用。
参考资料
-
Resolve header include circular dependencies in C++, stackoverflow-http://stackoverflow.com/questions/625799/resolve-header-include-circular-dependencies-in-c
-
C++交叉引用问题,互相包含头文件容易出问题,所以头文件能不包含就不包含, 开源中国. http://my.oschina.net/jlmpp/blog/135929
-
C++中前置声明的应用与陷阱, CSDN. http://blog.csdn.net/yunyun1886358/article/details/5672574
网友评论