C++ 编程规范:101条规则准则与最佳实践
引言
C++ 是一门强大而复杂的语言,能高效控制硬件,也能写出优雅抽象。然而,正因其复杂性,项目中若缺乏统一规范,极易陷入混乱、难维护、易出错的泥潭。
本文总结了 101条 C++ 编程规范与最佳实践,涵盖 命名、结构、内存管理、多线程、异常、安全、性能等多个维度,旨在帮助开发者构建高质量、可维护、可扩展的 C++ 项目。
一、命名与风格(Rules 1–10)
# | 规则 | 简要建议 |
---|
1 | 类名使用大驼峰 PascalCase | 如 ThreadPool ,提高可辨识性 |
2 | 变量名使用小驼峰 camelCase | 例:logFilePath ,区分于类名 |
3 | 常量用 ALL_CAPS + _ 分隔 | 强化不变含义,如 MAX_BUFFER_SIZE |
4 | 命名需语义明确 | 避免 data , tmp ,使用 configFilePath 更清晰 |
5 | 缩写仅限通用缩写 | 如 idx , buf ,尽量使用全称提升可读性 |
6 | 函数名用动词 + 名词 | 例如 loadConfig() 表意清晰 |
7 | 命名空间用小写 | 如 network::socket ,避免歧义 |
8 | 接口类加 I 前缀(可选) | 如 ILogger ,强调为接口 |
9 | 成员变量加前缀/后缀 | m_ 或 _ 表示成员变量,增强可读性 |
10 | 用 enum class 替代裸 enum | 强类型更安全,防止命名冲突 |
二、代码结构与风格(Rules 11–20)
# | 规则 | 简要建议 |
---|
11 | 每个头文件只声明一个模块 | 避免多义性,便于编译与复用 |
12 | 使用 #pragma once 或 include guard | 防止重复包含 |
13 | include 顺序:本地 > 第三方 > STL | 增强可读性与可维护性 |
14 | 避免头文件中包含过多实现 | 使用前向声明可减少依赖 |
15 | 类/函数应单一职责 | 有助于测试与扩展 |
16 | 控制函数长度 < 60 行 | 超过建议拆分子函数 |
17 | 控制每个文件长度 < 2000 行 | 模块化设计更清晰 |
18 | 每行不超过 120 字符 | 保证阅读体验,特别在 review 时 |
19 | 使用 4 空格缩进,禁止制表符 | 统一格式,防止跨平台混乱 |
20 | 所有控制结构都用 {} 包围 | 防止隐式逻辑错误,如单行 if 陷阱 |
三、类设计与对象管理(Rules 21–30)
# | 规则 | 建议 |
---|
21 | 所有成员变量应为私有 | 使用 getter/setter 访问 |
22 | 提供合理构造/析构函数 | 保证资源初始化与释放对称 |
23 | 禁用复制/移动时应 = delete | 明确意图,防止误用 |
24 | 用 explicit 阻止隐式转换 | 如 explicit Config(std::string path) |
25 | 避免裸指针作为成员 | 使用 unique_ptr /shared_ptr 安全管理 |
26 | 构造函数不做复杂逻辑 | 仅初始化,不处理业务 |
27 | 基类析构函数应为 virtual | 否则 delete 派生类有 UB |
28 | 优先使用组合而非继承 | 组合更灵活、低耦合 |
29 | 不使用多重继承(除非纯接口) | 降低复杂度,避免菱形继承问题 |
30 | 避免深层继承结构 | 建议控制在 2 层以内 |
四、函数与模板(Rules 31–40)
# | 规则 | 建议 |
---|
31 | 参数多于 3 个建议封装结构体 | 提高可读性与扩展性 |
32 | 参数传递规则明确 | 小型值传递,大型对象引用 |
33 | 函数返回值推荐智能指针或值传递 | 避免裸指针和资源泄露 |
34 | 函数要写用途注释 | 特别是公共接口或库函数 |
35 | 模板逻辑应轻量,避免过多嵌套 | 编译时间压力大时尤需注意 |
36 | 合理使用 auto 简化类型 | 不影响语义的地方使用 |
37 | 模板中加入 static_assert 限定 | 增强类型安全性 |
38 | 使用 constexpr 提升编译期能力 | 如常量计算函数 |
39 | 控制模板递归深度 | 编译器对深层模板支持有限 |
40 | 模板尽可能放 header 中定义 | 避免链接错误(ODR 问题) |
五、内存管理(Rules 41–50)
# | 规则 | 建议 |
---|
41 | 禁止裸 new/delete | 用 make_unique /make_shared 替代 |
42 | 所有资源管理用 RAII | 让析构自动释放资源 |
43 | 禁止手动 free/close | 封装在类中自动释放 |
44 | 指针拥有权应清晰 | 避免 ownership 混乱 |
45 | 避免 shared_ptr 在多线程竞争 | 使用 atomic_shared_ptr 或避免频繁共享 |
46 | 使用工具检测泄漏 | 如 Valgrind、ASan |
47 | 使用智能指针区分 shared/unique 语义 | 更清晰,更安全 |
48 | 不要传值传递 shared_ptr | 用 const& 降低引用计数开销 |
49 | 使用容器代替裸数组 | STL 容器更安全 |
50 | 类封装资源释放逻辑 | 遵守 RAII,职责清晰 |
六、异常处理与错误传递(Rules 51–60)
# | 规则 | 建议 |
---|
51 | 尽量避免使用异常 | 推荐 error code / Result<T> 结构 |
52 | 异常必须 catch 并处理 | 记录日志,避免 silent fail |
53 | 不使用 catch (...) | 易隐藏逻辑错误 |
54 | 构造函数中不抛异常 | 否则无法确定对象是否成功创建 |
55 | 明确错误处理模块 | 集中统一处理错误 |
56 | 注释中注明错误返回 | 增强调用方对异常的理解 |
57 | 编写无副作用函数 | 降低调试/测试成本 |
58 | 日志输出必须有上下文 | 包括文件名/函数名/线程信息 |
59 | 接口错误向上传递 | 不要在底层吞掉问题 |
60 | 异常路径不得影响主逻辑性能 | 异常处理应轻量快捷 |
七、多线程与并发(Rules 61–70)
# | 规则 | 建议 |
---|
61 | 封装线程操作 | 避免裸用 std::thread |
62 | 原子操作使用 std::atomic | 避免竞态条件 |
63 | 使用细粒度锁或无锁结构 | 提升性能,减少死锁 |
64 | 使用 lock_guard 管理锁 | 自动加锁释放 |
65 | 不捕获局部引用传入线程 | 否则线程中变量悬空 |
66 | 避免死锁 | 控制锁顺序,使用 std::scoped_lock |
67 | 避免全局变量并发读写 | 用线程局部存储或加锁保护 |
68 | 构建线程池封装并发任务 | 避免线程爆炸与资源浪费 |
69 | 不得在对象析构前 detach 线程 | 否则存在野线程 |
70 | 使用条件变量控制等待 | 避免忙等浪费 CPU |
八、性能优化(Rules 71–80)
# | 规则 | 建议 |
---|
71 | 优化热点路径 | 代码中使用 likely / unlikely |
72 | 用 std::move 转移资源 | 防止不必要的拷贝 |
73 | 使用 emplace_back | 避免对象额外构造拷贝 |
74 | 使用 reserve 预分配空间 | 降低 reallocation 成本 |
75 | 避免频繁申请释放内存 | 推荐对象池或内存复用 |
76 | 避免虚函数热路径中使用 | 可用策略模式等替代 |
77 | 小函数可使用 inline | 减少函数调用开销 |
78 | 注意 ABI 兼容性 | 跨平台或多版本部署需考虑 |
79 | 无序容器快于有序容器 | unordered_map 通常优于 map |
80 | 使用 string_view 避免拷贝 | 尤其在字符串解析场景中 |
九、安全与健壮性(Rules 81–90)
# | 规则 | 建议 |
---|
81 | 所有输入必须校验合法性 | 防止越界、注入等问题 |
82 | 检查整数溢出风险 | 使用安全加法函数 |
83 | 禁止数组越界访问 | 用 at() 或容器封装 |
84 | 使用 RAII 管理资源 | 防止内存泄漏或悬空指针 |
85 | IO 操作必须检查返回值 | 否则容易逻辑错误 |
86 | 不在库中使用 exit/abort | 破坏调用者行为 |
87 | 库中不处理 UI/日志 | 由上层决定策略 |
88 | 接口遵循最小权限原则 | 降低攻击面与耦合 |
89 | 使用静态分析工具辅助检查 | 如 clang-analyzer, cppcheck |
90 | 禁止未定义行为写法 | 避免 UB 问题,如越界指针、悬空引用等 |
十、工程实践与工具链(Rules 91–101)
# | 规则 | 建议 |
---|
91 | 接入持续集成(CI) | 自动编译与检查保障质量 |
92 | 使用单元测试框架 | 推荐 GTest/GMock |
93 | 使用代码覆盖率工具 | 识别未测试路径 |
94 | 强制统一代码格式化工具 | 推荐 clang-format |
95 | 接入内存检测工具 | 如 AddressSanitizer |
96 | 使用 CMake 管理构建 | 跨平台统一构建系统 |
97 | 单元测试覆盖率 >= 80% | 提升可靠性 |
98 | 所有代码需 Code Review | 防止低级错误 |
99 | 接入日志和监控模块 | 如 Prometheus、Grafana |
100 | 所有模块应可独立构建测试 | 降低耦合度 |
101 | 每半年重审一次规范 | 适应团队与项目演化 |