概述
为什么要减少#includ的数量?
因为当一个头文件被包含的同时也引入了一项新的依赖,另外头文件的修改就要重新编译。
我们为什么要包括头文件?
通常是我们需要获得某个类型的定义(definition)。
在什么情况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?
当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。
C++中的函数签名(function signature):包含了一个函数的信息,包括函数名、参数类型、参数个数、顺序以及它所在的类和命名空间
实例
有类型A和类型C,在哪些情况下在A需要C的定义:
1、A继承至C
2、A有一个类型为C的成员变量
3、A有一个类型为C的指针的成员变量
4、A有一个类型为C的引用的成员变量
5、A有一个类型为std::list的成员变量
6、A有一个函数,它的签名中参数和返回值都是类型C
7、A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
8、A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
9、C和A在同一个名字空间里面
10、C和A在不同的名字空间里面
解析:
1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。
2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请
看Hurb的Exceptional C++。
3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。
5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,
在包括一个list,vector,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。
6,不需要,只要我们没有使用到C。
7,需要,我们需要知道调用函数的签名。
8,8的情况比较复杂,直接看代码会比较清楚一些。
C & doToC(C & );
C & doToC2(C & c) {
return doToC(c);