C++核心指导原则: 哲学部分
C++ Core Guidelines 是由 Bjarne Stroustrup 与其他专家共同制定的一系列关于如何高效, 安全地使用 C++的指导原则. 在 C++ Core Guidelines 的 Philosophy(P)部分, 提供了关于编写高质量, 可维护和安全的 C++代码的高层次指导原则.
C++ Core Guidelines 整理目录
P: Philosophy
P.1: Express ideas directly in code
- 翻译: 直接在代码中表达想法.
- 原因: 清晰明了的代码更容易理解和维护. 通过使用有意义的变量名和函数名, 可以减少注释的需求.
比如这个示例中, 使用 Year
, Month
和 Day
来构造 Date
对象, 而不是使用 int 作为参数. 连续三个 int
类型的参数很容易出现传参错误而且编译器无法发现. 而使用强类型则可以最大程度上避免传参错误.
使用 std::find
而不是 for
循环, 可以更好的表达意图.
P.2: Write in ISO standard C++
- 翻译: 使用 ISO 标准 C++编写代码.
- 原因: 遵循标准可以确保代码的可移植性和兼容性, 避免依赖特定编译器的行为. 同样需要避免未定义行为.
P.3: Use libraries wherever possible
- 翻译: 尽可能使用库.
- 原因: 库通常经过广泛测试和优化, 使用它们可以提高代码质量和性能, 并减少重复劳动.
P.4: Avoid writing new code when a library can do the job
- 翻译: 当库可以完成任务时, 避免编写新代码.
- 原因: 重用现有库可以减少错误, 提高开发效率, 并利用已经存在的优化. 用自己造的轮子容易翻车.
P.5: Prefer simple and direct code over clever optimizations
- 翻译: 优先使用简单直接的代码而不是聪明的优化.
- 原因: 简单的代码更容易理解和维护.
P.6: Do not optimize prematurely
- 翻译: 不要过早优化.
- 原因: 过早优化可能导致代码复杂化且难以维护, 只有在实际性能瓶颈明确时才进行优化. 但是也不要做明显的劣化.
P.7: Make interfaces precisely and strongly typed
- 翻译: 使接口精确且强类型化.
- 原因: 强类型的接口可以帮助捕获错误并提高代码的安全性.
P.8: Keep it simple
- 翻译: 保持简单.
- 原因: 简单的设计和实现更容易理解和维护, 减少了引入错误的可能性.
P.9: Design for correctness, efficiency, and maintainability
- 翻译: 设计时考虑正确性, 效率和可维护性.
- 原因: 良好的设计可以在保证正确性的前提下提高效率, 并简化未来的维护工作.
P.10: Use const when possible
- 翻译: 尽可能使用
const
. - 原因:
const
可以帮助编译器优化代码, 并防止意外修改数据. 参考const vs constexpr
P.11: Use constexpr when possible
- 翻译: 尽可能使用
constexpr
. - 原因: 编译时常量表达式可以在编译时计算结果, 减少运行时开销. 参考constexpr, consteval, 和 constinit 简要介绍
P.12: Use inline functions for small, frequently called functions
- 翻译: 对于小而频繁调用的函数使用内联函数.
- 原因: 内联函数可以减少函数调用的开销, 特别是在高频调用的情况下.
P.13: Use RAII (Resource Acquisition Is Initialization) for resource management
- 翻译: 使用 RAII 进行资源管理.
- 原因: RAII 可以确保资源的正确获取和释放, 减少忘记释放资源导致的问题.
P.14: Prefer value semantics over reference semantics when possible
- 翻译: 尽可能优先使用值语义而不是引用语义.
- 原因: 值语义可以减少对对象生命周期的管理需求, 并简化代码逻辑.
P.15: Minimize dependencies between modules
- 翻译: 最小化模块之间的依赖关系.
- 原因: 减少依赖可以提高模块的独立性和可测试性, 并降低耦合度.
P.16: Use namespaces to avoid name clashes
- 翻译: 使用命名空间避免名称冲突.
- 原因: 命名空间可以隔离不同模块的标识符, 防止名称冲突. 当一个项目逐渐变大之后, 用到的第三方库也会越来越多, 如果不用命名空间隔离, 那么很容易发生名称冲突(即一个函数名或者类名在多个模块中都被定义).
P.17: Avoid global variables
- 翻译: 避免全局变量.
- 原因: 全局变量容易引发竞态条件和数据竞争, 增加调试难度. 需要注意的是单例(Singleton)也是一个全局变量.
补充材料
Amdahl’s Law
Amdahl’s Law 是并行计算领域中用于预测通过增加处理器数量所能达到的加速比的一个公式. 它由计算机科学家 Gene Amdahl 在 1967 年提出, 主要用于评估并行计算系统的效率和性能提升极限.
Amdahl’s Law 表明, 程序的加速比受到其串行部分的限制, 即无论增加多少处理器或并行执行单元, 程序的最大加速比都受限于不能并行化的那部分工作.
$$S_{latency}(s) = \frac{1}{(1 - P) + \frac{P}{s}} $$其中$S_{latency}$是加速比, $P$是可并行化的工作比例, 而$s$是并行处理单元的数量(比如 CPU 核心数).
根据这个定律, 如果一个程序中有一定比例的任务必须按顺序执行(不可并行化), 那么即使剩余任务可以完全并行化, 随着并行处理单元数量的增加, 整体加速效果将逐渐接近某个极限值. 这是因为随着并行处理单元的增加, 那些无法被并行化的任务所占的比例相对于整个执行时间变得越来越大. 这个给我们的指导就是一定要抓住主要矛盾, 抓住耗时最大的地方进行优化.
举例介绍
假定一个程序员花了不小代价将一部分代码提高了 10 倍(s = 10), 这个很厉害了. 但是这部分代码之前在总的运行时长中占比仅为 1%(即$P=0.01$). 那么最终这个程序的加速比将接近$S_{latency}(s) = \frac{1}{(1 - 0.01) + \frac{0.01}{10}} = \frac{1}{0.99 + 0.001} \approx 1.009$. 性能提升了 0.9%. 很明显这样做的价值不大. 原因就是他找错了优化方向, 抓住了个芝麻粒大小的点在疯狂输出.
Tags: