C++代码规范

Huan Lee Lv5

google C++规范中文文档

头文件

  1. self-contained头文件

  2. #define保护: 命名格式为 <PROJECT>_<PATH>_<FILE>_H

  3. 尽量避免使用前置声明(纯粹的声明, 没有定义)

  4. 只有当函数小于10行时才将其定义为内联函数

  5. #include的路径和顺序

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

    • 使用标准的include顺序: 相关头文件, C 库, C++ 库, 其他库的头文件, 本项目内的头文件

作用域

  1. 命名空间: 鼓励在 .cc 文件中使用匿名命令空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。

    • 在命名空间的最后注释出命名空间的名字。

    • 用命名空间把文件包含, gflags 的声明/定义, 以及类的前置声明以外的整个源文件封装起来

    • 不使用 using 指示 引入整个命名空间的标识符号。

    • 不要在头文件中使用 命名空间别名 除非显式标记内部命名空间使用。因为任何在头文件中引入的命名空间都会成为公开API的一部分。

  2. **匿名命名空间和静态变量:**在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 static 。但是不要在 .h 文件中这么做。

  3. **非成员函数、静态成员函数和全局函数:**使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.

  4. 局部变量: 将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化, 离第一次使用越近越好

    • 有一个例外, 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数. 这会导致效率降低.
  5. 静态和全局变量: 禁止定义静态储存周期非POD变量,禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植。

  1. 构造函数的职责: 不要在构造函数中调用虚函数, 也不要在无法报出错误时进行可能失败的初始化.

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

    • 一个例外是, 拷贝和移动构造函数不应当被标记为 explicit, 因为它们并不执行类型转换
  3. **可拷贝类型和可移动类型:**如果你的类型需要, 就让它们支持拷贝 / 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.

  4. **结构体 VS. 类:**仅当只有数据成员时使用 struct, 其它一概使用 class.

  5. 继承: 使用组合常常比使用继承更合理. 如果使用继承的话, 定义为**public**继承.

    • 如果你的类有虚函数, 则析构函数也应该为虚函数.

    • 在声明重载时, 请使用 overridefinal 或 virtual 的其中之一进行标记.

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

  7. 接口: 接口是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制).

  8. 运算符重载: 除少数特定环境外, 不要重载运算符. 也不要创建用户定义字面量.

  9. 存取控制: 将 所有 数据成员声明为 private, 除非是 static const 类型成员.

  10. 声明顺序: 将相似的声明放在一起, 将 public 部分放在最前.

  • 建议以如下的顺序: 类型 (包括 typedefusing 和嵌套的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数, 数据成员.

函数

  1. **输入和输出:**我们倾向于按值返回, 否则按引用返回。 避免返回指针, 除非它可以为空.

  2. **编写简短函数:**倾向于编写简短, 凝练的函数. 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.

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

  4. **函数重载:**若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.

  5. **缺省参数:**只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与 函数重载 遵循同样的规则. 一般情况下建议使用函数重载.

  6. **函数返回类型后置语法:**只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.

来自google的奇技

  1. **所有权与智能指针:**动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.

    • 如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用 std::unique_ptr 来明确所有权传递

    • 如果没有很好的理由, 则不要使用共享所有权. 这里的理由可以是为了避免开销昂贵的拷贝操作, 但是只有当性能提升非常明显, 并且操作的对象是不可变的时候, 才能这么做. 如果确实要使用共享所有权, 建议于使用 std::shared_ptr .

  2. 使用cpplint检查风格错误

命名约定

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

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

    • C++ 文件要以 .cc 结尾, 头文件以 .h 结尾. 专门插入文本的文件则以 .inc 结尾

    • 不要使用已经存在于 /usr/include 下的文件名

    • 定义类时文件名一般成对出现, 如 foo_bar.h 和 foo_bar.cc, 对应于类 FooBar.

  3. 类型命名: 类型名称的每个单词首字母均大写, 不包含下划线MyExcitingClassMyExcitingEnum.

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

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

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

  7. 命名空间命名: 命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称. 要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突. 不使用缩写

  8. 枚举命名: 枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME. 单独的枚举值应该优先采用 常量 的命名方式. 但  方式的命名也可以接受.

  9. 宏命名: 通常 不应该 使用宏. 如果不得不用, 其命名像枚举命名一样全部大写, 使用下划线

注释

  1. 注释风格: 使用 // 或 /* */, 统一就好,  //  常用.

  2. 文件注释: 在每一个文件开头加入版权公告. 文件注释描述了该文件的内容.

  3. 类注释: 每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显.

    • 类注释应当为读者理解如何使用与何时使用类提供足够的信息, 同时应当提醒读者在正确使用此类时应当考虑的因素. 如果类有任何同步前提, 请用文档说明. 如果该类的实例可被多线程访问, 要特别注意文档说明多线程环境下相关的规则和常量使用.

    • 如果你想用一小段代码演示这个类的基本用法或通常用法, 放在类注释里也非常合适.

    • 如果类的声明和定义分开了(例如分别放在了 .h 和 .cc 文件中), 此时, 描述类用法的注释应当和接口定义放在一起, 描述类的操作和实现的注释应当和实现放在一起.

  4. 函数注释: 函数声明处的注释描述函数功能; 定义处的注释描述函数实现.

    函数声明处注释的内容:

    • 函数的输入输出.
    • 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
    • 函数是否分配了必须由调用者释放的空间.
    • 参数是否可以为空指针.
    • 是否存在函数使用上的性能隐患.
    • 如果函数是可重入的, 其同步前提是什么?
  5. 变量注释: 通常变量名本身足以很好说明变量用途. 某些情况下, 也需要额外的注释说明.

  6. 如果有非变量的参数(例如特殊值, 数据成员之间的关系, 生命周期等)不能够用类型与变量名明确表达, 则应当加上注释.

  7. 特别地, 如果变量可以接受 NULL 或 -1 等警戒值, 须加以说明.

  8. 实现注释:

  9. **代码前注释:**巧妙或复杂的代码段前要加注释.

  10. **行注释:**比较隐晦的地方要在行尾加入注释. 在行尾空两格进行注释.

  11. **函数参数注释:**如果函数参数的意义不明显, 考虑用下面的方式进行弥补:

    • 如果参数是一个字面常量, 并且这一常量在多处函数调用中被使用, 用以推断它们一致, 你应当用一个常量名让这一约定变得更明显, 并且保证这一约定不会被打破.
    • 考虑更改函数的签名, 让某个 bool 类型的参数变为 enum 类型, 这样可以让这个参数的值表达其意义.
    • 如果某个函数有多个配置选项, 你可以考虑定义一个类或结构体以保存所有的选项, 并传入类或结构体的实例. 这样的方法有许多优点, 例如这样的选项可以在调用处用变量名引用, 这样就能清晰地表明其意义. 同时也减少了函数参数的数量, 使得函数调用更易读也易写. 除此之外, 以这样的方式, 如果你使用其他的选项, 就无需对调用点进行更改.
    • 用具名变量代替大段而复杂的嵌套表达式.
    • 万不得已时, 才考虑在调用点用注释阐明参数的意义.
  12. TODO注释: 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份标识和与这一 TODO 相关的 issue.

  13. DEPRECATED注释: 通过弃用注释(DEPRECATED comments)以标记某接口点已弃用. 可以放在接口声明前, 或者同一行.

  14. 标点和语法: 注释的通常写法是包含正确大小写和结尾句号的完整叙述性语句. 大多数情况下, 完整的句子比句子片段可读性更高. 短一点的注释, 比如代码行尾注释, 可以随意点, 但依然要注意风格的一致性.

格式

直接使用google风格的代码格式化工具即可.

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

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

  3. 只使用空格, 每次缩进 2 个空格.

  4. 函数声明与定义: 返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行

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

  6. 函数调用: 要么一行写完函数调用, 要么在圆括号里对参数分行, 要么参数另起一行且缩进四格. 如果没有其它顾虑的话, 尽可能精简行数, 比如把多个参数适当地放在同一行里.

  7. 条件语句: 尽可能不省略大括号. 只有if (condition) dosomething(); 语句很短时可以写为一行.

    1
    2
    3
    4
    5
    6
    7
    if (condition) {  // 圆括号里没有空格.
    ... // 2 空格缩进.
    } else if (...) { // else 与 if 的右括号同一行.
    ...
    } else {
    ...
    }
  8. 循环和switch: switch 语句可以使用大括号分段, 以表明 cases 之间不是连在一起的. 在单语句循环里, 括号可用可不用. 空循环体应使用 {} 或 continue. 在单语句循环里, 括号可用可不用

  9. 指针和引用表达式: 句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格. 在声明指针变量或参数时, 星号与类型或变量名紧挨都可以

  10. 布尔表达式: 如果一个布尔表达式超过 标准行宽 , 断行方式要统一一下.

  11. 预处理指令**:**预处理指令不要缩进, 从行首开始.

  12. 类格式: 访问控制块的声明依次序是 public:protected:private:, 每个都缩进 1 个空格.

  13. 构造函数初始值列表: 构造函数初始化列表放在同一行或按四格缩进并排多行.

  14. 命名空间: 命名空间内容不缩进.

  15. 垂直留白越少越好. 基本原则是: 同一屏可以显示的代码越多, 越容易理解程序的控制流. 当然, 过于密集的代码块和过于疏松的代码块同样难看

其他C++特性

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

  2. 右值引用: 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.

  3. 禁止使用 RTTI(运行时类型识别), 如使用 typeid 或者 dynamic_cast .只允许在单元测试中使用RTTI

  4. 使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式

  5. 只在记录日志时使用流.

  6. 对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.

  7. 在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化.

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

  9. 代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记

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

  11. 尽可能用 sizeof(varname) 代替 sizeof(type).

  12. 用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方.

  13. 适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。

  14. 只使用 Boost 中被认可的库.

    • Call Traits  : boost/call_traits.hpp

    • Compressed Pair  : boost/compressed_pair.hpp

    • <The Boost Graph Library (BGL)  : boost/graph, except 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.hppboost/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.hppboost/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_map, and boost/container/flat_set

  • Title: C++代码规范
  • Author: Huan Lee
  • Created at : 2023-08-29 21:50:34
  • Updated at : 2024-02-26 04:53:15
  • Link: https://www.mirthfullee.com/2023/08/29/notion-C++代码风格-0e5515b0/
  • License: This work is licensed under CC BY-NC-SA 4.0.