1.
头文件(Header
Files)
通常,每个源文件(.cc file)文件都应该有一个与之关联的头文件(.h file)文件。当然,有一些常见例外,比如只有一个main()函数的单元测试和小源文件。
正确地使用头文件可以使代码的可读性、体积和性能有一个大的提升。
通过下面的规则,你将了解大量使用头文件的缺陷。
1.1
#define保护(#include guard)
每个头文件都应该有一个#define保护以防止它被多次包含。而且符号的命名最好是以下形式:<PROJECT>_<PATH>_<FILE>_H_。
尽管保护各不相同,但它们应该以完整的项目资源目录为基础。比如foo项目中的foo/src/bar/baz.h文件应该这样保护:
#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // |
1.2
头文件的依赖关系(Header File Dependencies)
当一个前置声明足够时,不要使用#include。
当你包含一个头文件时,便引入了头文件依赖关系,每当这个头文件改变时,代码都需要重新编译。而且,如果这些头文件还包含其他头文件,任何改变都将导致包含该头文件的代码重新编译。因此,我们提倡最小化包含,尤其是头文件包含头文件的情形。
你可以通过使用前置声明来明显地减少自定义头文件包含其他头文件的数量。比如,如果你的头文件使用File类而不需要知道File类的声明,就可以前置声明File类,而不需要使用#include “file/base/file.h”
如何在不访问其定义的情况下使用类Foo呢?
1.
声明数据成员Foo* 或者Foo&;
2.
声明以Foo为参数,和/或返回值的函数。(有一个例外:如果参数是Foo或者const
Foo&,有一个隐式单参数构造函数,这种情况下我们需要引入完全定义来支持自动类型转换)。
3.
声明静态的Foo数据成员,这是因为静态数据成员在类定义外定义。
另一方面,如果你的类是Foo的子类或者包括一个Foo类型的数据成员,你必须包含该头文件。
有时使用指针成员(或使用更好的智能指针)代替对象成员更合理。然而,这会使代码的可读性复杂化并影响性能,所以,当仅以最小化包含文件数为目的时,尽量不要这么做。
通常,.cc文件需要知道其所用类的具体实现,因此需要包含一些头文件。
注意:如果你在.cc文件中使用Foo标识符,你应该自己定义Foo,要么通过一个#include命令,要么通过一个前置声明。然而有一个例外:如果在myfile.cc中使用Foo,在myfile.h中#include(或者前置声明)Foo也可以。
1.3
内联函数(Inline Functions)
当函数很小(比如10行或者更少)时才定义函数为内联。
内联定义:通过内联,编译器会在调用处将函数展开为代码,而不通过通常的函数调用机制。
利:由于内联函数通常很小,所以可产生更高效的目标代码。尽量内联类成员访问和修改函数(getters and setter)和其他一些简短,对性能要求关键的函数。
弊:过量使用内联将使程序性能受损。视其规模,内联一个函数即可能使其代码量增加,也可能使其代码量减少。内联一个小的类成员访问函数(getter)通常会减小其代码量,然而内联一个很大的函数则会显著增加其代码量。现代处理器由于使用了指令缓存,在运行小规模代码时将更快。
结论:
一个好的经验法则是,当一个函数超过10行时,不要使用内联。注意析构函数,它们常常因不明显的成员或者基类析构调用比看起来的规模大。
另一个有用的经验法则是,内联一个包含有循环或者开关指令的函数常常是不划算的(除非在极特殊情况才会执行这些循环和开关指令)。
知道这一点很重要:一些函数常常不能被内联,即使被声明成这样,比如虚函数和递归函数常常不能被内联。内联一个虚函数的主要原因是将 它们的定义放在类定义中,或者在类定义中说明它们的行为,比如类成员访问器和修改器。
1.4
内联头文件(The –inl.h Files)
如有必要,你可以使用-inl前缀来定义复杂的内联函数。
应该在头文件中定义内联函数,这样,编译器才能将其代码复制到调用处。然而,实现代码通常应该包含在.cc文件:头文件一般不包含实现代码,除非为了改善可读性或者出于性能的考虑。
如果一个内联函数的定义很短,包含很少(如果有的话)的逻辑判断,则应在头文件中实现它们。比如,类成员访问器和修改器应该在类定义中实现。为方便定义和调用,一些复杂的内联函数也可在头文件中定义,当这些函数使头文件变得太过臃肿时,增加一个-inl.h文件来单独定义它们。-inl.h头文件使内联函数的实现和类的定义分开,同时又允许在需要的时候包含其实现。
-inl.h的另一个用途是定义函数模板。这将使你的函数模板定义可读性更好。
另外,不要忘了,-inl.h文件也是需要#define保护的。
1.5
函数参数次序(Function Parameter Ordering)
当定义一个函数时,其参数次序应该是:输入、输出。
一个C/C++函数的参数无外乎输入、输出或者兼具两者。输入参数常常是数值或者常引用,而输出或者出入参数则是非const指针。定义参数次序时,通常将所有输入参数置于输出参数之前。尤其注意,不要简单在把新参数加在参数列表最后,要将新输入参数置于所有输出参数之前。
然而,这不是一个一成不变的规则,比如出入参数(一般是类或结构体)。
1.6
包含的命名和次序(Names and Order of includes)
为增加可读性并避免隐蔽的依赖关系,请使用标准的包含次序:C库、C++库、其他库头文件、自定义头文件。
所有项目的头文件应该按其资源目录降序排列,且不要使用Unix的简略目录表示法(.表示当前目录,..表示父目录)。比如,google-awesome-project/src/base/logging.h应该这样被包含:
#include “base/logging.h” |
如果dir/foo.cc的主要功能是实现和测试dir2/foo2.h中的内容,可以这样安排:
1.
dir2/foo2.h
2.
C系统文件
3.
C++系统文件
4.
其他库头文件
5.
本项目头文件
这种首选次序可以减少隐蔽的依赖关系。我们希望每一个头文件都可以独立编译。最简单的方法就是确保它们在.cc文件中是第一次被包含。
dir/foo.cc和dir2/foo2.h通常在一个目录中(比如base/basictypes_test.cc和base/basictypes.h),当然,也可以在不同目录中。
各部分内,最好按字母表顺序排列。
比如google-awesome-project/src/foo/internal/fooserver.cc可以这样排序包含文件:
#include “foo/public/fooserver.h” //首选位置
#include <sys/types.h> #include <unistd.h>
#include <hash_map> #include <vector>
#include “base/basictypes.h” #include “base/commandlineflags.h” |