1. 通用原则

  • 总体规范参考 Google 规范,略有改动 详见 $\rightarrow$ https://google.github.io/styleguide/cppguide.html

  • 使用或借助 AI 写代码请务必自己理解每个代码都在干什么

  • 行宽不超过 80 字符,设置 vscode 行宽 80 字符线:

    1. Ctrl(Cmd) + , 或者 File $\rightarrow$ Preferences $\rightarrow$ Settings
    2. 搜索:rulerEditor: Ruler一栏编辑 settings.json:
1{
2  "editor.rulers": [80] // 原有配置的基础上添加这一行
3}
  • .cc 文件与.cpp 文件本质没区别,统一使用.cpp 后缀

  • 代码可读性第一,避免过度优化或炫技

  • 变量、函数、类名应自解释,避免无意义缩写(如 a, tmp1),除非是广泛接受的缩写(如 id, url, cmd)

  • 错误处理必须显式,不能忽略可能的异常、错误码或返回值

  • 全英文注释:统一,正式,美观,且标点只使用半角标点(英文字符)

    1. 标点(: , ; . ? !)前不加空格,后加一个空格,
      () [] {} "" '')外侧加空格内测不加
      - _)前后不加空格,其余所有运算符前后都有空格
    2. 在函数名和左括号之间不要留空格。在参数列表的逗号后留一个空格。 int add(a, b);
    3. 避免在圆括号、方括号或花括号内部紧邻留空格。 list[1:5] dict = {'key': value}
  • 声明与初始化尽可能的靠近,如

1int i = function();
  • 整体尽量避免使用宏

  • 以下标准可以存在极少的例外

2 C++

2.1 头文件,声明规范

2.1.1 显式包含代替隐式包含:

 1// foo.h
 2#include "bar.h" // 因为参数中使用了 Bar,所以必须包含 Bar 的定义
 3void useBar(Bar b);
 4
 5// foo.cpp
 6#include "foo.h"
 7// 即使 foo.h 已经包含了 bar.h,这里最好也显式包含一次(根据规则后半部分)
 8#include "bar.h" // 显式包含,表明我依赖 Bar
 9
10void useBar(Bar b) {
11    // ... 实现代码
12}

2.1.2 尽可能避免前向声明:

1// b.h:
2struct B {};
3struct D : B {};
4
5// good_user.cc:
6#include "b.h"
7void f(B*);
8void f(void*);
9void test(D* x) { f(x); }  // 调用 f(B*)

2.1.3 include 的名称与顺序:

 1// C/C++标准库 系统 第三方库使用尖括号
 2// 其他使用引号
 3// 顺序如下
 4#include "foo/server/fooserver.h"       // 主要关联头文件
 5
 6#include <sys/types.h>                  // 系统头文件
 7#include <unistd.h>
 8
 9#include <string>                       // 标准库头文件
10#include <vector>
11
12#include "base/basictypes.h"            // 项目内部头文件
13#include "foo/server/bar.h"
14#include "third_party/absl/flags/flag.h"

2.1.4 变量声明

  • 最小化作用域 (Minimize Scope): 局部变量应该在使用前的最小可能作用域内声明和定义。

  • 声明时初始化 (Initialize on Declaration): 变量在声明时必须进行初始化。

  • 尽可能使用: 尽可能使用 const 或 constexpr 来声明变量。不要使用宏!

  • 指针和引用: 将 * 和 & 放在类型旁边,而不是变量名旁边。

  • 只有在能够提高可读性或避免冗余类型名时才使用 auto

2.1.5 函数声明

  • 参数顺序:输入参数(const或值),输入输出参数,输出参数

  • 默认参数:必须将它们放在声明中(即在 .h 文件中),而不是定义中(即在 .cc 文件中)。

2.2 命名规范

2.2.1 命名空间规范

  • 不使用缩进: 命名空间内的代码块不进行额外的缩进。

  • 花括号: { 放在命名空间名称的同一行。

  • 命名空间结束注释: 命名空间结束的 } 后面必须加上一行注释,指明结束的是哪个命名空间。

 1namespace mynamespace {
 2// 空行
 3// 代码... (Code...)
 4
 5class MyClass {
 6public:
 7  void DoSomething();
 8};
 9
10void MyClass::DoSomething() {
11  // 实现细节... (Implementation details...)
12}
13
14} // namespace mynamespace  // 在命名空间的结尾处添加注释
15                            // 注释内容与命名空间名称完全一致

2.2.2 命名空间使用规范

  • 禁止 using-directive: 绝对不要使用 using namespace foo; 这样的指令。

  • 禁止 inline namespace: 不要使用内联命名空间。

1// 明确禁止,污染命名空间
2using namespace abc;
  • 明确使用 std::: 对于 C++ 标准库的符号,必须始终使用 std:: 前缀,例如 std::stringstd::vector
1// 允许:仅引入单个名称
2using foo::bar;
 1// 匿名命名空间示例 (Example Unnamed Namespace in a .cc file)
 2
 3namespace {
 4// 这里的变量和函数仅对当前 .cc 文件可见
 5int internal_counter = 0;
 6
 7void internal_helper_function() {
 8  // ...
 9}
10} // namespace

2.2.3 各类型命名规则

类型规则示例
头文件小写 + 下划线my_useful_class.h
类、结构体、枚举、类型别名单词首字母大写ProcessData, MaxVelocity
函数、方法小写 + 下划线do_some_thing()
全大写 + 下划线A_INT_ABC
常量首单词首字母小写,其余单词首字母大写kBufferSize
类成员全小写且结尾加下划线name*, user_count*

2.3 格式与缩进

 1class Sensor {
 2 public:                   // 单个空格
 3  void readData() {        // 单个空格
 4    if (isConnected_) {    // 两个空格
 5      fetchData();         // 两个空格
 6    }
 7  }
 8
 9 private:                  // 单个空格
10  bool isConnected_{false};// 单个空格
11};

2.4 内存与资源管理

2.4.1 RAII 原则

  • 将资源的生命周期绑定到对象的生命周期上

  • 资源获取: 资源(如内存、文件句柄、锁、网络连接)在对象构造时获取。

  • 资源释放: 资源在对象析构时自动释放。

  • 必须使用 RAII 封装器来管理互斥锁 (std::mutex)。

1void GoodFunction() {
2    std::vector<int> data(100);  // 自动管理内存
3    // 即使抛出异常,vector 析构函数也会自动释放内存
4}

2.4.2 智能指针

  • 绝对禁止使用原始指针(new / delete)进行内存管理。必须使用智能指针来管理动态分配的对象。
1auto ptr1 = std::make_unique<MyClass>(arg1, arg2);
2auto ptr2 = std::make_shared<MyClass>(arg1, arg2);

2.4.3 禁止使用宏定义代替常量或内联函数(除非必要)

  • 具名常量
1constexpr int kDefaultBufferSize = 1024;
2const double kPi = 3.1415926535;
  • 枚举常量
1enum class ErrorCode {
2  kSuccess = 0,
3  kTimeout = 1,
4  kHardwareFailure = 2
5};
  • 内联函数

只有当函数非常短(不超过 10 行)时,才允许在头文件中定义

1template <typename T>
2inline T Max(T a, T b) {
3  // 行为与普通函数一致,不会重复求值
4  return (a > b) ? a : b;
5}

2.5 异常与错误处理

2.5.1 禁止使用 C++的异常

  • 完全避免 try, catch, 和 throw 关键字

2.5.2 可预期的运行时错误: Status 或错误码

  • 函数调用失败但程序可以继续运行的情况,返回错误状态,调用者决定恢复或传播。
 1// 使用 Status 对象作为返回值
 2absl::Status OpenFile(const std::string& path);
 3
 4// 调用端处理:
 5absl::Status status = OpenFile("/path/to/file");
 6if (!status.ok()) {
 7  LOG(ERROR) << "Failed to open file: " << status.message();
 8  // 根据错误类型进行恢复或直接返回
 9  return status; 
10}

2.5.3 不可恢复的编程错误: 断言 (Assertions)

  • 用于检查那些如果失败则表明代码逻辑存在 bug 的条件。
1// 使用 CHECK 宏检查不可恢复的编程错误
2void ProcessData(const std::vector<int>& data) {
3  // 检查 data 向量不应该为空。如果为空,说明调用者或上游代码有 Bug。
4  CHECK(!data.empty()) << "Input data must not be empty."; 
5
6  // ... 正常逻辑
7}

2.6 注释

2.6.1 公告接口

  • 只在声明处注释: Doxygen 注释块只放在头文件(.h)的声明上方。不要在实现文件(.cc)中重复。

  • 避免冗余: Doxygen 标签的内容应该为使用代码的人提供价值,而不是重复代码中显而易见的信息。例如,如果函数名为 CalculateSum,就没必要在 @brief 中写“计算总和”。

  • 公共接口使用 Doxygen 风格注释:

 1/**
 2 * @brief 计算两个整数的和。
 3 *
 4 * 该函数执行简单的算术加法,不会产生溢出检查。
 5 *
 6 * @param a 第一个加数。
 7 * @param b 第二个加数。
 8 * @return 整数 a 和 b 的总和。
 9 */
10int Add(int a, int b);
标签 (Tag)作用 (Purpose)示例 (Example)
@brief简要说明。用于对代码实体进行简短的一句话描述,通常作为文档的标题。/// @brief 初始化连接池。
@param参数描述。描述函数的输入参数。* @param count 要创建的连接数量。
@return返回值描述。描述函数的返回值。* @return 成功返回 Status::OK。
@note注意事项。用于描述使用时的特殊注意事项或约束。* @note 该操作不是原子性的。
@warning警告。用于描述潜在的危险或已知的限制。* @warning 传入 null 会导致崩溃。
@tparam模板参数描述。描述模板函数的类型参数。* @tparam T 容器中元素的类型。

2.6.2 文件头部注释

  • 文件内容描述: 紧随其后,对文件内容进行简要描述。

  • .h 文件: 描述其中声明的类和函数的作用、用途以及使用方式。

  • .cpp 文件: 描述实现细节、复杂算法或任何需要注意的实现决策。

2.6.3 注释内容

  • 描述性而非祈使性: 注释应该描述代码是做什么的(例如:“Opens the file”),而不是命令它去做什么(例如:“Open the file”)。

  • 解释原因: 最好的注释是解释为什么这样做,特别是对于非显而易见的、复杂的或有陷阱的代码。

  • TODO:描述必须清晰、完整, 能够解释为什么需要这个 TODO(临时解决方案、已知缺陷、未来工作),以及完成它需要做什么。

1// TODO(责任人): 详细描述待办事项

3. 范式

  • 注意:实际编辑的时候请使用英文注释

  • 范式内包含很多无意义注释(为了理解规范),实际编辑时不要添加

3.1 文件树

1.
2├── main.cpp
3├── sensor_data.cpp
4└── sensor_data.h

3.2 main.cpp

 1// main.cpp
 2#include "sensor_data.h"  // Project internal header
 3
 4#include <iostream>  // Standard library header
 5#include <string>    // Standard library header
 6
 7// Use constexpr instead of macros for constants
 8// Constant naming: k prefix, PascalCase
 9constexpr int kMainReturnCodeSuccess = 0;
10
11// Allowed: only introducing a single name
12using sensor::SensorData;
13
14int main() {
15  // Declaration and initialization as close as possible
16  SensorData sensor_instance;
17
18  // Variable names should be self-explanatory
19  const int requested_data_size = 50;
20
21  // Function/method call
22  std::string data_output = sensor_instance.get_data(requested_data_size);
23
24  std::cout << "Data received: " << data_output << std::endl;
25
26  sensor::reset_sensor_connection();
27
28  // If using an enum class, scope is required (ErrorCode::kSuccess)
29  // Punctuation (:) before no space, after one space
30  // Use the k-prefixed constant for comparison
31  if (static_cast<int>(sensor::ErrorCode::kSuccess) == kMainReturnCodeSuccess) {
32    // Punctuation (.) before no space, after one space
33    std::cout << "Program finished successfully." << std::endl;
34  }
35
36  return kMainReturnCodeSuccess;
37}

3.3 sensor_data.cpp

 1// sensor_data.cpp
 2// Line width does not exceed 80 characters
 3#include "sensor_data.h"  // Primary associated header
 4
 5#include <algorithm>  // Standard library header for std::min
 6#include <iostream>   // Standard library header
 7#include <unistd.h>   // System header
 8
 9namespace sensor {
10
11// Function naming: snake_case
12std::string SensorData::get_data(int data_size) {
13  // Avoid meaningless abbreviations, such as 'a' or 'tmp'
14  const int current_size = std::min(data_size, kMaxDataSize);
15
16  // Operators have spaces before and after, parentheses have space on the outside and no space on the inside
17  if (current_size < 10) {
18    // Error handling must be explicit (this is only an example)
19    // The actual code should use absl::Status
20    std::cerr << "ERROR: Data size is too small." << std::endl;
21    return "";
22  }
23
24  // Class member access
25  user_count_++;
26
27  // Use named constants instead of macros
28  std::string result = name_ + ": " + std::to_string(current_size);
29  return result;
30}
31
32void reset_sensor_connection() {
33  // All English comments: unified, formal, and punctuation only includes half-width punctuation
34  // The connection is resetting now by sleeping for 1 second.
35  // Note: For production code, the return value of sleep should be checked
36  //       to ensure error handling is explicit.
37  sleep(1);
38}
39
40}  // namespace sensor

3.4 sensor_data.h

 1// sensor_data.h
 2#ifndef FOO_SENSOR_DATA_H_
 3#define FOO_SENSOR_DATA_H_
 4
 5#include <string>  // Standard library header
 6
 7// Constant naming: k prefix, first word lowercase, rest of words PascalCase
 8constexpr int kMaxDataSize = 80;
 9
10// Enum naming: PascalCase
11// Use strong-typed enum (enum class)
12enum class ErrorCode {
13  kSuccess = 0,
14  kTimeout = 1,
15  kHardwareFailure = 2
16};
17
18namespace sensor {  // Namespace: all lowercase
19
20// Class/Struct naming: PascalCase
21class SensorData {
22 public:
23  /**
24   * @brief Retrieves the current sensor reading.
25   *
26   * This is a filtered and calibrated value.
27   *
28   * @param data_size The desired size of the returned data.
29   * @return The processed sensor data as a string.
30   */
31  // Function naming: snake_case
32  std::string get_data(int data_size);
33
34 private:
35  // Class member naming: all lowercase, ending with underscore
36  std::string name_{"DefaultSensor"};  // Declaration and initialization near usage
37  int user_count_{0};
38};
39
40// Function naming: snake_case
41void reset_sensor_connection();
42
43}  // namespace sensor
44
45#endif  // FOO_SENSOR_DATA_H_