Google 风格指南
本文是 Google 开源项目风格指南 的简要小结,但是小部分加入了一些我的注释
头文件
除了 main() 函数和单元测试文件外,每个源文件都应当有对应的头文件
头文件应该是 自包含 的,所谓 自包含 是指外部引用时不需要为特殊场合包含额外的头文件
内联函数和模板应当在头文件中添加定义而不是在源文件中
备注
对于 MSVC 而言,内联函数必须在头文件中实现,否则会编译失败
如果某函数模板为所有相关模板参数显式实例化,或是某个类的私有成员,那么它就只能在源文件中实现
头文件应当有 #define 或 #prgma once 的保护,以防止头文件被多次包含
小技巧
#define 的命名格式为
<PROJECT>_<PATH>_<FILE>_H_,其中 <PATH> 相对于项目的源代码路径除了本项目的实体外,函数、类模板等应当避免使用 前置声明
备注
使用前置声明引入的类是不完全类型,我们只能
使用类的指针或引用
声明 以不完全类型作为参数或返回值的函数
不完全类型不能用来创建对象或引用其函数。因此一般在头文件中使用前置声明
不要对标准库中的类使用前置类型声明
只有函数代码少于十行时才会被定义为内联函数 ( 析构函数 、 递归函数 和 虚函数 不应当定义为内联函数)
#include 应当按照以下顺序包含头文件:
源文件对应的头文件
C 系统头文件
C++ 系统头文件
其他库中的头文件
本项目中的头文件
按条件包含的头文件
在每个类别中应当插入空行进行分隔
作用域
命名空间
在 源文件 中建议使用 匿名命名空间 或 static 声明 。但是头文件不允许
具名的命名空间其名字应当基于项目名或相对路径
禁止使用 using 和 内敛命名空间
禁止在头文件中使用 命名空间别名
命名空间别名应当只在实现中使用
函数和变量
非成员函数应当总是放在命名空间中
不要使用类的静态函数模拟命名空间的效果
类的静态方法应当只和类的实例或静态数据相关
变量的作用域应当尽量缩小。只在需要使用变量时才声明它
应当使用初始化的方式声明变量而不是先声明再赋值(当在循环中使用对象时例外)
禁止创建非 POD 类型的 static 变量和全局变量 ( constexpr 变量除外)
禁止使用含有副作用的函数初始化 POD 全局变量
禁止使用函数返回值初始化 POD 变量(除非函数返回值不涉及任何全局变量)
备注
上述三条主要是防止在多编译单元中变量初始化顺序的不确定性。但在同一个编译单元内,静态初始化优先于动态初始化,初始化顺序与声明顺序相同。
为了改善静态变量和全局对象析构的不确定性,可以使用 quick_exit() 代替 exit() 终止程序,其不会执行析构过程。
类
不要在构造函数中以任何形式(直接调用或通过 init() 间接调用)调用虚函数
不要在构造函数中执行过多的逻辑相关的初始化
编译器提供的默认构造函数不会对变量进行初始化
不要在无法报告错误的情况下在 构造函数 中调用可能会出错的函数
构造函数的地址无法被获得
构造函数中不得报告非致命错误
不要定义隐式类型转换。转换运算符和单参数构造函数应当使用 explicit 修饰
备注
如果单参数构造函数没有使用 explicit 那么就无法判断这个函数是用作类型转换还是创建对象的。
如果不需要类支持拷贝和移动,就把它们删掉。
这里请参阅 可拷贝类型和可移动类型
子类重载虚函数时也应该加上 virtual 关键字
只有仿函数和在仅有数据成员时使用结构体,否则使用类
除非是公有继承,否则一律使用组合
当出现多重继承时,最多只能有一个基类是具体类,其他基类必须都为接口类。(除了 部分情况 )
备注
所谓接口类,就是只有纯虚函数和静态函数,没有非静态数据成员,没有定义任何带参数的、非 protect 的构造函数。
接口类建议使用 Interface 结尾。
尽量不要重载运算符,也不要创建用户自定义字面量
没有副作用的二元运算符不应当定义为成员函数
不要重载 && 、 || 、 , 和 & 。也不要重载 operator””
除了 static const 类型外,所有数据成员声明为 private
控制域应当遵循 public -> protected -> private,将相似的声明放在最后
存取函数一般内联在头文件中。
提示
所谓存取函数,是指存取数据成员的 getter 和 setter 。而不是容器的 push 和 pop
函数
在函数声明时,输入参数(通常由 const 修饰)要放到输出参数(通常是非 const 指针)前面
函数体的长度一般不应该大于 40 行
所有按引用传递的参数必须加上 const
若要使用函数重载, 应当让用户一看函数签名就知道怎么用, 而不是花心思猜测调用的重载函数到底是哪一种。这一规则也适用于构造函数
备注
如果函数的参数数量相同,与其重载一个函数(例如 Append() ),不如在函数名上加上参数信息。(比如 AppendString、AppendInt 等)
禁止在纯虚函数中使用默认参数。在函数重载时 必须 保证默认参数的值相同。
备注
对于默认参数而言,以下需要注意:
默认参数时函数重载的另一种语义,一般情况下使用函数重载而不是默认参数
默认参数在每个调用点都需要进行重新求值,这会导致生成的代码迅速膨胀
缺省参数会干扰函数指针, 导致函数签名与调用点的签名不一致 。 而函数重载不会导致这样的问题
如果使用函数指针调用带有默认参数的函数,那么默认参数无效。这是因为默认参数是在运行时求值,而函数调用是编译时。
默认参数值一旦改变,所有设计到函数的文件都必须重新编译
只为可以缺省的参数提供默认值,不要为了调用时省劲就用默认参数 1
除以下情况外 非常不建议 使用默认参数: 2
源文件中的静态函数或者匿名空间函数
构造函数
用来模拟变长数组
例如:
// 通过空 AlphaNum 以支持四个形参 string StrCat(const AlphaNum &a, const AlphaNum &b = gEmptyAlphaNum, const AlphaNum &c = gEmptyAlphaNum, const AlphaNum &d = gEmptyAlphaNum);
只有在不方便使用前置类型声明时采用后置类型声明
备注
C++ 的前置类型声明的方法是:
int foo(int x);
后置类型写法是:
auto foo(int x) -> int;
只有在前置类型声明不方便的时候才使用后置类型声明(比如 Lambda 表达式)
只有在移动构造和移动赋值时才使用右值引用。不要使用 std::forward
不允许使用变长数组和 alloca()
不允许使用 C 风格的类型转换
只在记录日志时使用流,其他时候使用 printf 和 read
备注
事实上,流使我们在打印时不在关心对象的类型,但是这也导致用错代码时编译器不会有任何警告。而且流不做特殊处理的时候效率是很低的。尽管我们可以一边用流一边用 printf ,但是根据 一致性原则 ,我们除了日志接口外,其余部分都用 printf 和 read 以统一 IO 接口。
对于迭代器和其他模板对象而言,只使用前置自增
尽可能使用 const 和 constexpr 。 const 建议放在类型前面。
在声明 const 对象的同时对对象进行初始化。
关键字 mutable 是线程不安全的,谨慎使用、
对于整型而言,只使用 int 、 int8_t 、 int64_t 等。(位于 cstdint )
64 位下的可移植性
备注
此部分主要针对打印,参见: 64 位下的可移植性
尽量使用内联函数、枚举和常量代替宏
不要在头文件中定义宏
只有在马上使用时才 #define ,之后立即 #undef
不要对一个已经存在的宏使用 #undef ,选择一个不会冲突的名称
不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为
不要用 ## 处理函数,类和变量的名字
对于空值而言:整数用 0 ,实数用 0.0 ,指针用 nullptr 或 NULL 、字符用 ‘\0’
尽量使用 sizeof(变量名) 而不是 sizeof(类型名)
在保证可读性良好的情况下使用 auto
auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。
使用列表初始化或双括号以避免歧义。使用列表初始化而不是圆括号以避免隐式类型转换。
Lambda 不要捕获全部,只捕获需要的部分
不要使用过于复杂的模板编程
Boost 只使用以下库:
Call Traits : boost/call_traits.hpp
Compressed Pair : boost/compressed_pair.hpp
<The Boost Graph Library (BGL) : boost/graph, 除了 serialization (adj_list_serialize.hpp) and parallel/distributed algorithms and data structures(boost/graph/parallel/* and boost/graph/distributed/*)
Property Map : boost/property_map.hpp
The part of Iterator that deals with defining iterators: boost/iterator/iterator_adaptor.hpp, boost/iterator/iterator_facade.hpp, and boost/function_output_iterator.hpp
The part of Polygon that deals with Voronoi diagram construction and doesn’t depend on the rest of Polygon: boost/polygon/voronoi_builder.hpp, boost/polygon/voronoi_diagram.hpp, and boost/polygon/voronoi_geometry_type.hpp
Bimap : boost/bimap
Statistical Distributions and Functions : boost/math/distributions
Multi-index : boost/multi_index
Heap : boost/heap
The flat containers from Container: boost/container/flat_ma*p, and *boost/container/flat_set
friend 是 赋权 语句而不是 声明 语句。友元的函数需要在类外面重新声明一遍
友元的声明应当与类定义放在一起,定义 可以 放在类的实现文件中。
命名约定
文件名不要使用大小写命名法,应当使用下划线或者连字符。一般使用下划线
类型命名应当使用驼峰命名法。不要包含下划线
变量使用匈牙利命名法,类的成员变量使用下划线结尾,但是结构体的不用
const 或 constexpr 等静态常量或者全局变量以 k 开头,使用驼峰命名法
函数使用驼峰命名法。存取函数使用匈牙利命名法并要求与变量名匹配。
备注
对于字母缩写而言,更倾向于将它们视为一个单词而不是全大写。例如使用 StartRpc() 而不是 StartRPC()
命名空间以小写字母命名。。
顶级命名空间的名字取决于项目名称
命名空间中的代码, 应当存放于和命名空间的名字匹配的文件夹或其子文件夹中
建议使用更独特的项目标识符 ( websearch::index, websearch::index_util) 而非常见的极易发生冲突的名称 (比如 websearch::util ).
宏应当使用全大写 + 下划线
枚举优先采用变量的命名方式,其次也可以采用宏的命名方式
如果你的实体与 C/C++ 已有实体相似,也可以参考现有命名策略 3
注释
注释使用 // 或 /**/ 都行,统一即可
每个文件开头加入版权公告
如果头文件声明了多个概念,则注释应当为头文件提供一个简要内容。详细的注释应当分散到代码中
除非类的功能非常明显,否则为类提供一份注释以描述其内容和 用法
如果类的声明和定义分开了, 此时, 描述类用法的注释应当和接口定义放在一起, 描述类的操作和实现的注释应当和实现放在一起.
类的数据成员、全局变量应当加上注释说明其用途。若变量可以接受 NULL 或 -1 等警戒值,也要加以说明
函数注释
函数声明处的注释描述函数功能; 定义处的注释描述函数实现
为函数提供注释,除非它的功能非常明显。注释使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”); 注释只是为了描述函数, 而不是命令函数做什么. 通常, 注释不会描述函数如何工作. 那是函数定义部分的事情。
一般函数声明处的内容为:
函数的输入输出.
对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
函数是否分配了必须由调用者释放的空间.
参数是否可以为空指针.
是否存在函数使用上的性能隐患.
如果函数是可重入的, 其同步前提是什么?
如果函数的实现过程中用到了很巧妙的方式, 那么在函数定义处应当加上解释性的注释. 例如, 你所使用的编程技巧, 实现的大致步骤, 或解释如此实现的理由。 举个例子, 你可以说明为什么函数的前半部分要加锁而后半部分不需要。
小技巧
不要 从头文件或其他地方的函数声明处直接复制注。 简要重述函数功能是可以的, 但注释重点要放在如何实现上。
对于函数实现较为复杂、晦涩的部分前加上注释
在作用比较奇怪的地方(例如函数返回)后加上注释,注释与代码空两格
函数调用时,在比较晦涩的地方使用枚举等具名变量代替布尔参数。在迫不得已的情况下也可以使用 /**/ 的方式加上注释
不要描述显而易见的代码,不要使用机器翻译作为注释
对于临时的、短期的解决方案,可以使用 TODO :
TODO 可以加入名字、邮件地址、bug ID 或其他信息
TODO 也可以加入截止日期
// TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke) change this to use relations. // TODO(bug 12345): remove the "Last visitors" feature
通过 DEPRECATED 注释和关键字 标记某函数的接口已被弃用。 DEPRECATED 后应该留下您的名字, 邮箱地址以及其他身份标识。废弃接口注释还应当引导如何使用此接口的替代方案。
格式
此部分依赖于个人和项目约定,也可参阅 格式
其他内容
以下内容是 Google 为了兼容已有代码或兼容社区成员而考虑的,请酌情考虑是否使用:
不允许使用异常
不允许使用 RTTI
参见