代码风格改善
google guide style
头文件
- 头文件应该能够自给自足(self-contained,也就是可以作为第一个头文件被引入),以
.h
结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以应以.inc
结尾。不允许分离出-inl.h
头文件的做法. - 所有头文件都应该有
#define
保护来防止头文件被多重包含, 命名格式当是:<PROJECT>_<PATH>_<FILE>_H_
. - 尽可能地避免使用前置声明。使用
#include
包含需要的头文件即可。 - 只有当函数只有 10 行甚至更少时才将其定义为内联函数.
- 使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C 库, C++ 库, 其他库的 .h, 本项目内的 .h.
dir2/foo2.h
(优先位置, 详情如下)- C 系统文件
- C++ 系统文件
- 其他库的
.h
文件 - 本项目内
.h
文件 - 按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。
- 有时,平台特定(system-specific)代码需要条件编译(conditional includes),这些代码可以放到其它 includes 之后。
作用域
- 鼓励在
.cc
文件内使用匿名命名空间或static
声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。- 不应该使用 using 指示 引入整个命名空间的标识符号。
- 不要在头文件中使用 命名空间别名 除非显式标记内部命名空间使用。因为任何在头文件中引入的命名空间都会成为公开API的一部分。
- 在
.cc
文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为static
。但是不要在.h
文件中这么做。 - 使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
- 将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化.
- 禁止定义静态储存周期非POD变量,禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植。
- 注意别在循环犯大量构造和析构的低级错误。
类
- 不要在构造函数中调用虚函数, 也不要在无法报出错误时进行可能失败的初始化.
- 在类型定义中, 类型转换运算符和单参数构造函数都应当用
explicit
进行标记. 一个例外是, 拷贝和移动构造函数不应当被标记为explicit
, 因为它们并不执行类型转换. 对于设计目的就是用于对其他类型进行透明包装的类来说, 隐式类型转换有时是必要且合适的. 这时应当联系项目组长并说明特殊情况. - 如果需要就让你的类型可拷贝 / 可移动. 作为一个经验法则, 如果对于你的用户来说这个拷贝操作不是一眼就能看出来的, 那就不要把类型设置为可拷贝. 如果让类型可拷贝, 一定要同时给出拷贝构造函数和赋值操作的定义, 反之亦然. 如果让类型可移动, 同时移动操作的效率高于拷贝操作, 那么就把移动的两个操作 (移动构造函数和赋值操作) 也给出定义. 如果类型不可拷贝, 但是移动操作的正确性对用户显然可见, 那么把这个类型设置为只可移动并定义移动的两个操作.
- 仅当只有数据成员时使用
struct
, 其它一概使用class
. - 所有继承必须是
public
的. 如果你想使用私有继承, 你应该替换成把基类的实例作为成员对象的方式. - 只有当所有父类除第一个外都是 纯接口类 时, 才允许使用多重继承. 为确保它们是纯接口, 这些类必须以
Interface
为后缀. - 接口是指满足特定条件的类, 这些类以
Interface
为后缀 (不强制). - 除少数特定环境外, 不要重载运算符. 也不要创建用户定义字面量.
- 将 所有 数据成员声明为
private
, 除非是static const
类型成员 (遵循 常量命名规则). 出于技术上的原因, 在使用 Google Test 时我们允许测试固件类中的数据成员为protected
. - 将相似的声明放在一起, 将
public
部分放在最前.
函数
- 我们倾向于按值返回, 否则按引用返回。 避免返回指针, 除非它可以为空.
- 在排序函数参数时, 将所有输入参数放在所有输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前.
- 我们倾向于编写简短, 凝练的函数.
- 所有按引用传递的参数必须加上
const
. - 事实上这在 Google Code 是一个硬性约定: 输入参数是值参或
const
引用, 输出参数为指针. 输入参数可以是const
指针, 但决不能是非const
的引用参数, 除非特殊要求, 比如swap()
. - 若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.
- 如果打算重载一个函数, 可以试试改在函数名里加上参数信息. 例如, 用
AppendString()
和AppendInt()
等, 而不是一口气重载多个Append()
. 如果重载函数的目的是为了支持不同数量的同一类型参数, 则优先考虑使用std::vector
以便使用者可以用 列表初始化 指定参数. - 只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与 函数重载 遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下.
- 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.
Google技巧
- 如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用
std::unique_ptr
来明确所有权传递, - 使用
cpplint.py
检查风格错误.
其他特性
-
所有按引用传递的参数必须加上
const
. -
只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用
std::forward
. -
若要用好函数重载,最好能让读者一看调用点(call site)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。
-
我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。
-
我们不允许使用变长数组和
alloca()
. -
我们允许合理的使用友元类及友元函数.
-
我们不使用 C++ 异常.
-
我们禁止使用 RTTI.
-
使用 C++ 的类型转换, 如
static_cast<>()
. 不要使用int y = (int)x
或int y = int(x)
等转换方式; -
只在记录日志时使用流.
-
对于迭代器和其他模板对象使用前缀形式 (
++i
) 的自增, 自减运算符. -
我们强烈建议你在任何可能的情况下都要使用
const
. 此外有时改用 C++11 推出的 constexpr 更好。 -
在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。
-
C++ 内建整型中, 仅使用
int
. 如果程序中需要不同大小的变量, 可以使用<stdint.h>
中长度精确的整型, 如int16_t
.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如int64_t
. 此外要留意,哪怕您的值并不会超出 int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。 -
代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记:
-
使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.
-
整数用
0
, 实数用0.0
, 指针用nullptr
或NULL
, 字符 (串) 用'\0'
.整数用
0
, 实数用0.0
, 这一点是毫无争议的.对于指针 (地址值), 到底是用
0
,NULL
还是nullptr
. C++11 项目用nullptr
; C++03 项目则用NULL
, 毕竟它看起来像指针。实际上,一些 C++ 编译器对NULL
的定义比较特殊,可以输出有用的警告,特别是sizeof(NULL)
就和sizeof(0)
不一样。字符 (串) 用
'\0'
, 不仅类型正确而且可读性好. -
尽可能用
sizeof(varname)
代替sizeof(type)
.使用
sizeof(varname)
是因为当代码中变量类型改变时会自动更新. 您或许会用sizeof(type)
处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式,这时用变量就不合适了。 -
用
auto
绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。 -
适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。
-
不要使用复杂的模板编程
-
只使用 Boost 中被认可的库.
-
适当用 C++11(前身是 C++0x)的库和语言扩展,在贵项目用 C++11 特性前三思可移植性。
命名约定
- 函数命名, 变量命名, 文件命名要有描述性; 少用缩写.
- 文件名要全部小写, 可以包含下划线 (
_
) 或连字符 (-
), 依照项目的约定. 如果没有约定, 那么 “_
” - 类型名称的每个单词首字母均大写, 不包含下划线:
MyExcitingClass
,MyExcitingEnum
. - 变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如:
a_local_variable
,a_struct_data_member
,a_class_data_member_
. - 声明为
constexpr
或const
的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. - 常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配:
MyExcitingFunction()
,MyExcitingMethod()
,my_exciting_member_variable()
,set_my_exciting_member_variable()
. - 命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称. 要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突.
- 枚举的命名应当和 常量 或 宏 一致:
kEnumName
或是ENUM_NAME
. - 你并不打算 使用宏, 对吧? 如果你一定要用, 像这样命名:
MY_MACRO_THAT_SCARES_SMALL_CHILDREN
. - 如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略.
注释
- 使用
//
或/* */
, 统一就好. - 在每一个文件开头加入版权公告.
- 每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显.
- 函数声明处的注释描述函数功能; 定义处的注释描述函数实现.
- 通常变量名本身足以很好说明变量用途. 某些情况下, 也需要额外的注释说明.
- 对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.
- 注意标点, 拼写和语法; 写的好的注释比差的要易读的多.
- 对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用
TODO
注释. - 通过弃用注释(
DEPRECATED
comments)以标记某接口点已弃用.
格式
- 每一行代码字符数不超过 80.
- 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.
- 只使用空格, 每次缩进 2 个空格.
- Lambda 表达式对形参和函数体的格式化和其他函数一致; 捕获列表同理, 表项用逗号隔开.
- 要么一行写完函数调用, 要么在圆括号里对参数分行, 要么参数另起一行且缩进四格. 如果没有其它顾虑的话, 尽可能精简行数, 比如把多个参数适当地放在同一行里.
- 您平时怎么格式化函数调用, 就怎么格式化 列表初始化.
- 倾向于不在圆括号内使用空格. 关键字
if
和else
另起一行. switch
语句可以使用大括号分段, 以表明 cases 之间不是连在一起的. 在单语句循环里, 括号可用可不用. 空循环体应使用{}
或continue
.- 句点或箭头前后不要有空格. 指针/地址操作符 (
*, &
) 之后不能有空格. - 如果一个布尔表达式超过 标准行宽, 断行方式要统一一下.
- 不要在
return
表达式里加上非必须的圆括号. - 用
=
,()
和{}
均可. - 预处理指令不要缩进, 从行首开始.
- 访问控制块的声明依次序是
public:
,protected:
,private:
, 每个都缩进 1 个空格. - 构造函数初始化列表放在同一行或按四格缩进并排多行.
- 命名空间内容不缩进.
- 水平留白的使用根据在代码中的位置决定. 永远不要在行尾添加没意义的留白.
- 垂直留白越少越好.