编码规范 - Google C++ Style Guide

代码规范图: google_code_style.png

https://blog.csdn.net/xu_fu_yong/article/details/123281297

初衷

  1. 统一的编程风格

  2. 风格 , 亦被称作可读性, 也就是指导 C++ 编程的约定

头文件

Self-contained 头文件

  • 所有头文件要能够自给自足。换言之,用户和重构工具不需要为特别场合而包含额外的头文件。

  • 如果 .h 文件声明了一个模板或内联函数,同时也在该文件加以定义。凡是有用到这些的 .cc 文件,就得统统包含该头文件,否则程序可能会在构建中链接失败。

#define 保护

  • 所有头文件都应该使用 #define 来防止头文件被多重包含, 命名格式当是: H .

  • 为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径

前置声明

  • 「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.

  • 尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。

  • 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。

内联函数

  • 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎内敛析构函数,其需要调用隐含成员以及基类析构函数

#include 的路径及顺序

  • 项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 .

头文件顺序

  1. 主体功能文件(这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。)
  2. C系统文件
  3. C++系统文件
  4. 其它库的.h文件
  5. 本项目内的.h文件

构造函数的职责

  • 构造函数不允许调用虚函数. 如果代码允许, 直接终止程序是一个合适的处理错误的方式. 否则, 考虑用 Init() 方法或工厂函数.

隐式类型转换

  • 不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字.

  • explicit 关键字可以用于构造函数或 (在 C++11 引入) 类型转换运算符, 以保证只有当目的类型在调用点被显式写明时才能进行类型转换

  • 隐式类型转换加强了可用性,但是有时候用户难以意识到发生了类型转换

可拷贝类型和可移动类型

  • 如果你的类型需要, 就让它们支持拷贝 / 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.

  • 可拷贝或者可移动方便了传值,但是有时候会导致对象切割令人困惑

  • 如果你的类不需要拷贝 / 移动操作, 请显式地通过在 public 域中使用 = delete 或其他手段禁用之.

结构体 VS. 类

  • 仅当只有数据成员时使用 struct (定义包含数据的被动式对象), 其它一概使用 class .

继承

  • 使用组合 (YuleFox 注: 这一点也是 GoF 在 <* 里反复强调的) 常常比使用继承更合理. 如果使用继承的话, 定义为 public 继承.

  • 继承的话优点是可以复用父类的代码,但是将子类实现分散在父类和子类之间(所以当确定是is-a的关系才使用继承)

多重继承

  • 只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类; 其它基类都是以 Interface 为后缀的 纯接口类 .

接口

  • 接口是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制).
  1. 只有纯虚函数 (“ =0 ”) 和静态函数 (除了下文提到的析构函数).
  2. 没有非静态数据成员.
  3. 没有定义任何构造函数. 如果有, 也不能带有参数, 并且必须为protected .
  4. 如果它是一个子类, 也只能从满足上述条件并以 Interface 为后缀的类继承.

运算符重载

  • 除少数特定环境外, 不要重载运算符.

存取控制

  • 将 所有 数据成员声明为 private , 除非是 static const 类型成员

声明顺序

  • 类定义一般应以 public: 开始, 后跟 protected:, 最后是 private:. 省略空部分.

  • 在各个部分中, 建议将类似的声明放在一起, 并且建议以如下的顺序: 类型 (包括 typedef, using 和嵌套的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数, 数据成员.

函数

参数顺序

  • 函数的参数顺序为: 输入参数在先, 后跟输出参数.

  • 输入参数通常是值参或 const 引用, 输出参数或输入/输出参数则一般为非 const 指针.

编写简短函数

  • 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.

引用参数

  • 所有按引用传递的参数必须加上 const .

  • 因为引用语法上是值变量但是却拥有指针的语义,声明const引用可以兼容const与非const

函数重载

  • 如果打算重载一个函数, 可以试试改在函数名里加上参数信息. 例如, 用 AppendString() 和 AppendInt() 等, 而不是一口气重载多个 Append() .

缺省参数

  • 少用缺省参数,其类似函数重载

  • 缺省参数是在每个调用点都要进行重新求值的, 这会造成生成的代码迅速膨胀.

函数返回类型后置语法

  • 在大部分情况下, 应当继续使用以往的函数声明写法, 即将返回类型置于函数名前. 只有在必需的时候 (如 Lambda 表达式) 或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法

其他 C++ 特性

引用参数

  • 函数参数列表中, 所有引用参数都必须是 const :

右值引用

  • 只在定义移动构造函数与移动赋值操作时使用右值引用 (使用类的右值进行构造,进行移动而不是拷贝)

  • 你可能会使用 std::move 来表示将值从一个对象移动而不是复制到另一个对象.

变长数组和 alloca()

  • 我们不允许使用变长数组和 alloca() .

  • 改用更安全的分配器(allocator),就像 std::vector 或 std::unique_ptr<T[]* .

友元

  • 友元扩大了 (但没有打破) 类的封装边界. 某些情况下, 相对于将类成员声明为 public , 使用友元是更好的选择

  • 友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类.

类型转换

  • 不要使用 C 风格类型转换. 而应该使用 C++ 风格
  1. 用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.
  2. 用 const_cast 去掉 const 限定符.
  3. 用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.

  • 不要使用流, 除非是日志接口需要. 使用 printf 之类的代替.

  • 流的优点是输出时不需要关心打印对象的类型,但是这个优点也是缺点,容易用错类型

前置自增和自减

  • 对简单数值 (非对象), 两种都无所谓. 对迭代器和模板类型, 使用前置自增 (自减).

const 用法

  • 我们强烈建议在任何可能的情况下使用 const
  1. 如果函数不会修改传你入的引用或指针类型参数, 该参数应声明为 const .
  2. 如果数据成员在对象构造之后不再发生变化, 可将其定义为 const .
  3. const位置可以放在int之前,因为在自然语言中形容词 ( const ) 是在名词 ( int ) 之前.

constexpr 用法

  • 在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化,即在编译时和运行时都不变。

  • 靠 constexpr 特性,方才实现了 C++ 在接口上打造真正常量机制的可能。

整型

  • C++ 内建整型中, 仅使用 int . 如果程序中需要不同大小的变量, 可以使用 <stdint.h* 中长度精确的整型, 如 int16_t .如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t .

  • 对于大整数, 使用 int64_t .

  • 不要使用 uint32_t 等无符号整型, 除非你是在表示一个位组而不是一个数值, 或是你需要定义二进制补码溢出.

预处理宏

现在可以用内联函数替代. 用宏表示常量可被 const 变量代替. 用宏 “缩写” 长变量名可被引用代替.

如果你要宏, 尽可能遵守:

  1. 不要在 .h 文件中定义宏.
  2. 在马上要使用时才进行 #define , 使用后要立即 #undef .
  3. 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
  4. 不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.
  5. 不要用 ## 处理函数,类和变量的名字。

0, nullptr 和 NULL

  • 整数用 0 , 实数用 0.0 , 指针用 nullptr 或 NULL , 字符 (串) 用 '\0' .

  • 对于指针 (地址值), 到底是用 0, NULL 还是 nullptr. C++11 项目用 nullptr; C++03 项目则用 NULL

sizeof

  • 尽可能用 sizeof(varname) 代替 sizeof(type) . 使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新.

auto

  • auto 只能用在局部变量里用。

命名约定

通用命名规则

  • 函数命名, 变量命名, 文件命名要有描述性; 少用缩写.

  • 模板参数的命名应当遵循对应的分类: 类型模板参数应当遵循 类型命名 的规则, 而非类型模板应当遵循 变量命名 的规则.

文件命名

  • 文件名要全部小写, 可以包含下划线 ( _ ) 或连字符 ( - ), 依照项目的约定. 如果没有约定, 那么 “ _ ” 更好.

类型命名

  • 所有类型命名 —— 类, 结构体, 类型定义 ( typedef ), 枚举, 类型模板参数 —— 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线.

变量命名

  • 变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用

常量命名

  • 声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合.

  • 所有具有静态存储类型的变量 (例如静态变量或全局变量, 参见 存储类型 ) 都应当以此方式命名.

函数命名

  • 常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配:

  • 一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc() 而非 StartRPC() ).

命名空间命名

  • 命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称.

  • 顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字.

格式

行长度

  • 每一行代码字符数不超过 80.

非 ASCII 字符

  • 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.

空格还是制表位

  • 我们使用空格(每次缩进 2 个空格)缩进. 不要在代码中使用制表符. 你应该设置编辑器将制表符转为空格.

函数声明与定义

  1. 左圆括号总是和函数名在同一行.
  2. 函数名和左圆括号间永远没有空格.
  3. 圆括号与参数间没有空格.
  4. 左大括号总在最后一个参数同一行的末尾处, 不另起新行.
  5. 右大括号总是单独位于函数最后一行, 或者与左大括号同一行.
  6. 右圆括号和左大括号间总是有一个空格.
  7. 缺省缩进为 2 个空格.
  8. 换行后的参数保持 4 个空格的缩进.

Lambda 表达式

  • Lambda 表达式对形参和函数体的格式化和其他函数一致; 捕获列表同理, 表项用逗号隔开.

函数调用

  • 如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:

  • 参数也可以放在次行, 缩进四格:

条件语句

  • 倾向于不在圆括号内使用空格. 关键字 if 和 else 另起一行.