Google编程风格指南(一):头文件相关

来源:互联网 发布:王健林惹到谁了 知乎 编辑:程序博客网 时间:2024/05/17 06:41
  1. 避免多重包含,用预编译宏
  #ifndef ...  #define ...    ...     #endif

头文件命名最好以其所在项目源代码树的全路径,例如: 项目foo中的头文件foo/src/bar/baz.h

  #ifndef FOO_BAR_BAZ_H_  #define FOO_BAR_BAZ_H_    ...     #endif

2 . 使用前置声明,降低编译依赖,防止修改一个头文件引发多米诺效应,能依赖声明的就不要依赖定义
使用前置声明(forward declarations)尽量减少.h 文件中#include 的数量。
一个头文件被包含的同时也引入了一项新的依赖(dependency),只要该头文件被修改,代码就要重新编译。如果你的头文件包含了其他头文件,这些头文件的任何改变也将导致那些包含了你的头文件的代码重新编译。因此,我们宁可尽量少包含头文件,尤其是那些包含在其他头文件中的。
使用前置声明可以显著减少需要包含的头文件数量。
举例说明:头文件中用到类 File,但不需要访问 File 的声明,则头文件中只需前置声明 class File; 无需 #include
“file/base/file.h” 。
在头文件如何做到使用类 Foo 而无需访问类的定义?
1) 将数据成员类型声明为 Foo *或 Foo &;
2) 参数、返回值类型为 Foo 的函数只是声明(但不定义实现);
3) 静态数据成员的类型可以被声明为 Foo,因为静态数据成员的定义在类定义之外。
另一方面,如果你的类是 Foo 的子类,或者含有类型为 Foo 的非静态数据成员,则必须为之包含头文件。
有时,使用指针成员(pointer members,如果是 scoped_ptr 更好 )替代对象成员(object members)的确更有意义。然而,这样的做法会降低代码可读性及执行效率。如果仅仅为了少包含头文件,还是不要这样替代的好。
当然,.cc 文件无论如何都需要所使用类的定义部分,自然也就会包含若干头文件。


3 内联函数
只有当函数只有 10 行甚至更少时才会将其定义为内联函数(inline function)。
定义( Definition ):当函数被声明为内联函数之后,编译器可能会将其内联展开,无需按通常的函数调用机制调用内联函数。

优点: 当函数体比较小的时候,内联该函数可以令目标代码更加高效。对于 存取函数( accessor 、mutator)以及其他一些比较短的关键执行函数。

缺点:滥用内联将导致程序变慢,内联有可能是目标代码量或增或减,这取决于被内联的函数的大小。内联较短小的存取函数通常会减少代码量,但内联一个很大的函数(译者注:如果编译器允许的话)将戏剧性的增加代码量。在现代处理器上,由于更好的利用指令缓存(instructioncache),小巧的代码往往执行更快。

结论:一个比较得当的处理规则是,不要内联超过 10 行的函数。对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用!

另一有用的处理规则:内联那些包含循环或 switch 语句的函数是得不偿失的,除非在大多数情况下,这些循环或 switch 语句从不执行。
重要的是,虚函数和递归函数即使被声明为内联的也不一定就是内联函数。通常,递归函数不应该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。
析构函数内联的主要原因是其定义在类的定义中,为了方便抑或是对其行为给出文档。


4.-inl.h 文件
复杂的内联函数的定义,应放在后缀名为-inl.h 的头文件中。
在头文件中给出内联函数的定义,可令编译器将其在调用处内联展开。然而,实现代码应完全放到.cc 文件中,我们不希望.h 文件中出现太多实现代码,除非这样做在可读性和效率上有明显优
势。如果内联函数的定义比较短小、逻辑比较简单,其实现代码可以放在.h 文件中。例如,存取函数的实现理所当然都放在类定义中。出于实现和调用的方便,较复杂的内联函数也可以放到.h
文件中,如果你觉得这样会使头文件显得笨重,还可以将其分离到单独的-inl.h 中。这样即把实现和类定义分离开来,当需要时包含实现所在的-inl.h 即可。
-inl.h 文件还可用于函数模板的定义,从而使得模板定义可读性增强。
要提醒的一点是,-inl.h 和其他头文件一样,也需要#define 保护。


5.函数参数顺序( n Function r Parameter Ordering )

定义函数时,参数顺序为:输入参数在前,输出参数在后。

C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输出(译者注:值被修改时 )。
输入参数一般传值或常数引用(const references ) ,输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为是新添加的参数,就将其置于最后,而应该依然置于输出参数之前。
这一点并不是必须遵循的规则,输入/输出两用参数(通常是类/结构体变量)混在其中,会使得
规则难以遵循。


6.包含文件的名称及次序

将包含次序标准化可增强可读性、避免隐藏依赖(hidden dependencies,译者注:隐藏依赖主要是指包含的文件中编译时),

次序如下:C 库、C++库、其他库的.h、项目内的.h。

项目内头文件应按照项目源代码目录树结构排列,并且避免使用 UNIX 文件路径 . (当前目录)和..(父目录)。例如,google-awesome-project/src/base/logging.h 应像这样被包含:

#include "base/logging.h"

dir/foo.cc 的主要作用是执行或测试 dir2/foo2.h 的功能,foo.cc 中包含头文件的次序如下:
dir2/foo2.h(优先位置,详情如下)
C 系统文件
C++系统文件
其他库头文件
本项目内头文件
这种排序方式可有效减少隐藏依赖,我们希望每一个头文件独立编译。最简单的实现方式是将其作为第一个.h 文件包含在对应的.cc 中。
dir/foo.cc 和 dir2/foo2.h 通常位于相同目录下(像 base/basictypes_unittest.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"#include "foo/public/bar.h"

译者:英语不太好,翻译的也就不太好。这一篇主要提到的是头文件的一些规则,总结一下:
. 1. 避免多重包含是学编程时最基本的要求;
. 2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;
. 3. 内联函数的合理使用可提高代码执行效率;
. 4. -inl.h 可提高代码可读性(一般用不到吧 :D );
. 5. 标准化函数参数顺序可以提高可读性和易维护性(对函数参数的堆栈空间有轻微影响,我 以
前大多是相同类型放在一起);
. 6. 包含文件的名称使用… . 和… .. 虽然方便却易混乱,使用比较完整的项目路径看上去很清晰、很 条
理,包含文件的次序除了美观之外,最重要的是可以减少隐藏依赖,使每个头文件在最需要 编译(对应源文件处 :D )的地方编译,有人提出库文件放在最后,这样出错先是项目内的文件,
头文件都放在对应源文件的最前面,这一点足以保证内部错误的及时发现了