Google的开源项目大多使用C++开发。每个C++程序员也都知道,C++具备不少强大的语言特性,但这种强大不可避免的致使它的复杂,这种复杂会使得代码更易于出现bug、难于阅读和维护。程序员
本指南的目的是经过详细阐述在C++编码时要怎样写、不要怎样写来规避其复杂性。这些规则可在容许代码有效使用C++语言特性的同时使其易于管理。编程
风格,也被视为可读性,主要指称管理C++代码的习惯。使用术语风格有点用词不当,由于这些习惯远不止源代码文件格式这么简单。缓存
使代码易于管理的方法之一是加强代码一致性,让别人能够读懂你的代码是很重要的,保持统一编程风格意味着能够轻松根据“模式匹配”规则推断各类符号的含义。建立通用的、必需的习惯用语和模式可使代码更加容易理解,在某些状况下改变一些编程风格可能会是好的选择,但咱们仍是应该遵循一致性原则,尽可能不这样去作。函数
本指南的另外一个观点是C++特性的臃肿。C++是一门包含大量高级特性的巨型语言,某些状况下,咱们会限制甚至禁止使用某些特性使代码简化,避免可能致使的各类问题,指南中列举了这类特性,并解释说为何这些特性是被限制使用的。性能
由Google开发的开源项目将遵守本指南约定。单元测试
注意:本指南并不是C++教程,咱们假定读者已经对C++很是熟悉。测试
一般,每个.cc文件(C++的源文件)都有一个对应的.h文件(头文件),也有一些例外,如单元测试代码和只包含main()的.cc文件。google
正确使用头文件可令代码在可读性、文件大小和性能上大为改观。编码
下面的规则将引导你规避使用头文件时的各类麻烦。spa
1. #define的保护
全部头文件都应该使用#define防止头文件被多重包含(multiple inclusion),命名格式当是: ___H_
为保证惟一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目foo中的头文件
foo/src/bar/baz.h
按以下方式保护:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
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)以及其余一些比较短的关键执行函数。
缺点:滥用内联将致使程序变慢,内联有多是目标代码量或增或减,这取决于被内联的函数的大小。内联较短小的存取函数一般会减小代码量,但内联一个很大的函数(译者注:若是编译器容许的话)将戏剧性的增长代码量。在现代处理器上,因为更好的利用指令缓存(instruction cache),小巧的代码每每执行更快。
结论:一个比较得当的处理规则是,不要内联超过10行的函数。对于析构函数应慎重对待,析构函数每每比其表面看起来要长,由于有一些隐式成员和基类析构函数(若是有的话)被调用!
另外一有用的处理规则:内联那些包含循环或switch语句的函数是得不偿失的,除非在大多数状况下,这些循环或switch语句从不执行。
重要的是,虚函数和递归函数即便被声明为内联的也不必定就是内联函数。一般,递归函数不该该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,好比递归层数在编译时多是未知的,大多数编译器都不支持内联递归函数)。析构函数内联的主要缘由是其定义在类的定义中,为了方便抑或是对其行为给出文档。
4. -inl.h文件
复杂的内联函数的定义,应放在后缀名为-inl.h的头文件中。
在头文件中给出内联函数的定义,可令编译器将其在调用处内联展开。然而,实现代码应彻底放到.cc文件中,咱们不但愿.h文件中出现太多实现代码,除非这样作在可读性和效率上有明显优点。
若是内联函数的定义比较短小、逻辑比较简单,其实现代码能够放在.h文件中。例如,存取函数的实现理所固然都放在类定义中。出于实现和调用的方便,较复杂的内联函数也能够放到.h文件中,若是你以为这样会使头文件显得笨重,还能够将其分离到单独的-inl.h中。这样即把实现和类定义分离开来,当须要时包含实现所在的-inl.h便可。
-inl.h文件还可用于函数模板的定义,从而使得模板定义可读性加强。
要提醒的一点是,-inl.h和其余头文件同样,也须要#define保护。
5. 函数参数顺序(Function 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
#include
#include
#include
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"
______________________________________
译者:英语不太好,翻译的也就不太好。这一篇主要提到的是头文件的一些规则,总结一下:
1. 避免多重包含是学编程时最基本的要求;
2. 前置声明是为了下降编译依赖,防止修改一个头文件引起多米诺效应;
3. 内联函数的合理使用可提升代码执行效率;
4. -inl.h可提升代码可读性(通常用不到吧:D);
5. 标准化函数参数顺序能够提升可读性和易维护性(对函数参数的堆栈空间有轻微影响,我之前大可能是相同类型放在一块儿);
6. 包含文件的名称使用.和..虽然方便却易混乱,使用比较完整的项目路径看上去很清晰、很条理,包含文件的次序除了美观以外,最重要的是能够减小隐藏依赖,使每一个头文件在“最须要编译”(对应源文件处:D)的地方编译,有人提出库文件放在最后,这样出错先是项目内的文件,头文件都放在对应源文件的最前面,这一点足以保证内部错误的及时发现了。