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 每半年重审一次规范 适应团队与项目演化