2.3 重复包含
由于头文件的传递性,有可能造成某上层头文件的重复包含。重复包含的头文件在展开后,将可能导致符号重定义,如:
// common.h
class Common
{
// ...
};
// h1.h
#include "common.h"
// h2.h
#include "common.h"
// test.cpp
#include "h1.h"
#include "h2.h"
int main()
{
return 0;
}
如果common.h中,有函数定义,结构体定义,类声明,外部变量定义等等。test.cpp中将展开两份common.h,编译时得到符号重定义的错误。而如果common.h中只有外部函数声明,则OK,因为函数可在多处声明,但只能在一处定义。关于类声明,C++类保持了C结构体语义,因此叫做”类定义”更为适合。始终记得,头文件只是一个公共代码的整合,这些代码会在预编译期替换到源文件中。
为了解决重复包含,C++头文件常用 #ifndef #define #endif或#pragma once来保证头文件不被重复包含。
2.4 交叉包含
C++中的类出现相互引用时,就会出现交叉包含的情况。如Parent包含一个Child对象,而Child类包含Parent的引用。因此相互包含对方的头文件,编译器展开Child.h需要展开Parent.h,展开Parent.h又要展开Child.h,如此无限循环,最终g++给出:error: #include nested too deeply的编译错误。
解决这个问题的方案是前向声明,在Child类定义前面加上 class Parent; 声明Parent类,而无需包含其头文件。前向声明不止可以用于类,还可以用于函数(即显式的函数声明)。前向声明应该被大量使用,它可以解决头文件带来的绝大多数问题,如效率性,传递性,重复包含,交叉包含等等。这一点有点像包(package)机制,需要什么,就声明(导入)什么。前向声明也有局限:仅当编译器无需知道目标类完整定义时。如下情形,类A可使用 class B;:
类A中使用B声明引用或指针;
类A使用B作为函数参数类型或返回类型,而不使用该对象,即无需知道其构造函数和析构函数或成员函数;
2.5 如何使用头文件
关于头文件使用的建议:
降低将文件间的编译依赖(如使用前向声明);
将头文件归类,按照特定顺序包含,如C语言系统头文件,C++系统头文件,项目基础头文件,项目头文件;
防止头文件重复编译(#ifndef or #pragma);
确保头文件和源文件的一致;
3.总结
C语言本身一些比较简单的特性,放在C++中却引起了很多麻烦,主要是因为C++复杂的语言特性:类,模板,各种宏… 举个例子来说,对于一个类A,它有一个私有函数,需要用到类B,而这个私有函数必须出现在类定义即头文件中,因此就增加了A头文件对B的不必要引用。这是因为C++类遵循C结构体的语义,所有类成员都必须出现在类定义中,”属于这个类的一部分”。这不仅在定义上造成不便,也在容易在语义上造成误解,事实上,C++类的成员函数不属于对象,它更像普通函数(虚函数除外)。










