C++ Core Guidelines 整理目录
- 哲学部分
- 接口(Interface)部分
- 函数部分
Function definition rules
F.1: “Package” meaningful operations as carefully named functions
- 翻译: 将有意义的操作"打包"为精心命名的函数.
- 原因: 有助于代码的可读性和维护性. 通过给函数起一个描述性的名字, 可以让其他开发者(或未来的你自己)更容易理解代码的目的.
F.2: A function should perform a single logical operation
- 翻译: 函数应该执行单一逻辑操作.
- 原因: 单一职责原则提高了代码的复用性和测试性. 如果一个函数只做一件事, 那么它出错的可能性较小, 也更容易进行单元测试.
F.3: Keep functions short and simple
- 翻译: 保持函数简短且简单.
- 原因: 简短的函数更易于理解和维护. 复杂度低的函数通常意味着它们更容易被正确实现, 并且在调试时更容易找到问题所在.
F.4: If a function might have to be evaluated at compile time, declare it constexpr
- 翻译: 如果一个函数可能需要在编译时求值, 声明其为 constexpr.
- 原因: 使用 constexpr 可以允许某些计算在编译期完成, 从而提高运行时性能. 此外, 这也有助于常量表达式的优化. 参考[constexpr, consteval, 和 constinit 简要介绍]({{< ref “/posts/2024-06-22-constexpr.md” >}})
F.5: If a function is very small and time-critical, declare it inline
- 翻译: 如果函数非常小且对时间敏感, 声明其为 inline.
- 原因: 内联函数可以减少函数调用带来的开销, 对于频繁调用的小函数特别有用. 但要注意, 过度使用可能会导致代码膨胀.
F.6: If your function must not throw, declare it noexcept
- 翻译: 如果你的函数不能抛出异常, 声明其为
noexcept
. - 原因: 标记函数为
noexcept
可以帮助编译器生成更高效的代码, 因为它知道该函数不会抛出异常, 从而可以在编译时进行更多的优化.
F.7: For general use, take T*
or T&
arguments rather than smart pointers
- 翻译: 对于一般用途, 使用
T*
或 T&
参数而不是智能指针. - 原因: 使用原始指针或引用可以使函数接口更加简洁, 并避免不必要的所有权管理复杂性. 只有在确实需要管理对象生命周期时才应使用智能指针.
F.8: Prefer pure functions
- 翻译: 优先使用纯函数.
- 原因: 纯函数(即没有副作用且结果仅依赖于输入参数的函数)更容易测试, 优化和理解.
F.9: Unused parameters should be unnamed
- 翻译: 未使用的参数应不命名.
- 原因: 如果某些参数在函数内部不会被使用, 可以通过省略名称来明确这一点, 减少混淆并提高代码的清晰度. 例如, 在虚函数重写中, 有时需要保留参数以匹配基类的签名.
F.10: If an operation can be reused, give it a name
- 翻译: 如果一个操作可以复用, 则为其命名.
- 原因: 将可复用的操作封装为具有描述性名称的函数可以提高代码的模块化程度, 便于维护和扩展. 此外, 它还提高了代码的可读性和重用性.
F.11: Use an unnamed lambda if you need a simple function object in one place only
- 翻译: 如果只需要在一个地方使用简单的函数对象, 则使用匿名 lambda 表达式.
- 原因: 匿名 lambda 表达式非常适合用于定义一次性的简单函数对象, 尤其是在算法中作为回调函数使用时. 这有助于保持代码紧凑, 并避免为一次性使用的函数创建多余的命名实体.
Parameter passing expression rules
F.15: Prefer simple and conventional ways of passing information
- 翻译: 优先使用简单和传统的信息传递方式.
- 原因: 简单直接的方法易于理解和维护, 减少错误发生的可能性, 并提高代码的可读性.
F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const
- 翻译: 对于"输入"参数, 对于便宜复制的类型按值传递, 其他则通过 const 引用传递.
- 原因: 所谓
in
参数, 是指只会读取而不修改的入参. 按值传递小型对象可以避免不必要的引用开销, 而对大型对象或非 POD 类型通过 const 引用传递可以节省复制成本并确保数据不变.
F.17: For “in-out” parameters, pass by reference to non-const
- 翻译: 对于"输入-输出"参数, 通过非
const
引用传递. - 原因: 所谓
in-out
参数, 是指那些会被读取并会被修改, 并且也是函数返回的一部分.比如: 使用非 const
引用可以直接修改传入的对象, 允许函数既接收也返回数据, 从而简化接口设计.
F.18: For “will-move-from” parameters, pass by X&&
and std::move
the parameter
- 翻译: 对于"将从中移动"的参数, 通过
X&&
传递并 std::move
参数. - 原因: 这种方法利用了 C++的移动语义, 可以有效地转移资源所有权, 避免不必要的复制, 提高性能.
F.19: For “forward” parameters, pass by TP&&
and only std::forward
the parameter
- 翻译: 对于"转发"参数, 通过
TP&&
传递, 并仅使用 std::forward
进行转发. - 原因: 使用完美转发(perfect forwarding)可以保留参数的原始类型信息(包括左值或右值), 从而避免不必要的复制并提高性能.
std::forward
确保在需要时正确地转发参数.
F.20: For “out” output values, prefer return values to output parameters
- 翻译: 对于"输出"结果值, 优先返回值而不是输出参数.
- 原因: 返回值比输出参数更直观且易于理解, 尤其是在函数只有一个输出结果时. 它使代码更具可读性, 并减少了错误的可能性, 因为不需要管理额外的引用或指针.
F.21: To return multiple “out” values, prefer returning a struct
- 翻译: 要返回多个"输出"值, 优先返回一个结构体.
- 原因: 当需要返回多个值时, 将这些值封装在一个结构体内可以使代码更加清晰和模块化. 相比于使用多个输出参数, 这种方法更容易维护, 并且可以更好地表示数据之间的关系.
F.60: Prefer T*
over T&
when “no argument” is a valid option
- 翻译: 当"无参数"是一个有效的选项时, 优先使用
T*
而不是 T&
. - 原因: 使用指针(
T*
)可以在某些情况下允许传递空值(nullptr
), 这在逻辑上可能表示没有有效值的情况. 而引用(T&
)不能为 null, 因此在这种场景下使用指针更为合适, 能够更灵活地处理这种情况.
Parameter passing semantic rules
F.22: Use T*
or owner<T*>
to designate a single object
- 翻译: 使用
T*
或 owner<T*>
来表示单个对象. - 原因: 明确指针所指向的对象类型有助于提高代码的清晰度和安全性. 使用
owner<T*>
可以明确表示该指针是对象的所有者, 从而帮助管理资源的生命周期.
F.23: Use a not_null<T>
to indicate that “null” is not a valid value
- 翻译: 使用
not_null<T>
来表示"空值"不是有效的值. - 原因:
not_null<T>
是一种类型安全的方式, 确保指针始终不为空. 这可以帮助捕获潜在的空指针引用错误, 并使代码更加健壮和易于理解.
F.24: Use a span<T>
or a span_p<T>
to designate a half-open sequence
- 翻译: 使用
span<T>
或 span_p<T>
来表示半开序列. - 原因:
span<T>
提供了一种安全且高效的方式来处理数组或容器的视图, 而无需复制数据. 它特别适用于需要传递数组片段的情况, 避免了手动管理指针和长度的风险.
F.25: Use a zstring
or a not_null<zstring>
to designate a C-style string
- 翻译: 使用
zstring
或 not_null<zstring>
来表示 C 风格字符串. - 原因:
zstring
是一种专门用于表示以空字符结尾的字符串的类型, 比传统的 C 风格字符串更安全. 使用not_null<zstring>
可以进一步确保指针不为空, 防止常见的空指针异常.
F.26: Use a unique_ptr<T>
to transfer ownership where a pointer is needed
- 翻译: 在需要指针的地方使用
unique_ptr<T>
来转移所有权. - 原因:
unique_ptr<T>
是一种智能指针, 用于唯一拥有一个对象并自动释放其资源. 它确保在任何时候只有一个所有者, 简化了资源管理并减少了内存泄漏的风险.
F.27: Use a shared_ptr<T>
to share ownership
- 翻译: 使用
shared_ptr<T>
来共享所有权. - 原因:
shared_ptr<T>
允许多个指针共享同一个对象的所有权, 并通过引用计数机制自动管理对象的生命周期. 这对于需要多个部分共享同一资源的情况非常有用, 但要注意避免循环引用导致的内存泄漏.
Value return semantic rules
F.42: Return a T*
to indicate a position (only)
- 翻译: 返回一个
T*
仅用于指示位置. - 原因: 使用指针返回位置而不是实际的数据结构, 有助于明确函数的意图, 并避免潜在的所有权问题.
F.43: Never (directly or indirectly) return a pointer or a reference to a local object
- 翻译: 绝不(直接或间接)返回指向局部对象的指针或引用.
- 原因: 局部对象在函数退出时会被销毁, 因此返回其地址会导致悬空指针, 这可能导致未定义行为.
F.44: Return a T&
when copy is undesirable and “returning no object” isn’t needed
- 翻译: 当不希望复制且不需要"返回无对象"时, 返回
T&
. - 原因: 返回引用可以避免昂贵的复制操作, 并提供对现有对象的有效访问, 但要注意避免返回临时对象的引用.
F.45: Don’t return a T&&
- 翻译: 不要返回一个
T&&
. - 原因: 返回右值引用(
T&&
)可能会导致悬空引用问题, 因为被引用的对象可能在函数返回后被销毁. 这会导致未定义行为, 并且难以调试.
F.46: int
is the return type for main()
- 翻译:
main()
函数的返回类型是int
. - 原因: 根据 C++标准,
main()
函数必须返回一个整数值, 通常用来表示程序的退出状态. 返回 0
通常表示成功执行, 非零值表示错误或异常情况. 我们有时候会看到void main()
的写法, 这个是 MSVC 的扩展, 不推荐使用; 而 GCC 和 Clang 则明确要求返回 int
.
F.47: Return T&
from assignment operators
- 翻译: 从赋值运算符(
=
)返回 T&
. - 原因: 赋值运算符应返回对当前对象的引用(
*this
), 以便支持链式赋值操作. 这样可以提高代码的灵活性和可读性.
F.48: Don’t return std::move(local)
- 翻译: 不要返回
std::move(local)
. - 原因: 对局部变量使用
std::move
是没有意义的, 因为局部变量在其作用域结束时会被销毁. 这样做不仅不会带来性能提升, 反而可能导致不必要的复杂性和潜在的错误.
F.49: Don’t return const T
- 翻译: 不要返回
const T
. - 原因: 返回常量类型的对象通常是不必要的, 除非有明确的需求. 返回非常量对象可以让调用者决定是否需要对其进行修改, 增加了灵活性.
Other function rules
F.50: Use a lambda when a function won’t do (to capture local variables, or to write a local function)
- 翻译: 当普通函数无法满足需求时使用 lambda 表达式(例如捕获局部变量或编写局部函数).
- 原因: Lambda 表达式提供了更灵活的方式, 特别是当你需要捕获外部变量或创建临时函数时. 它们可以使代码更加简洁和直观.
F.51: Where there is a choice, prefer default arguments over overloading
- 翻译: 在可以选择的情况下, 优先使用默认参数而不是重载.
- 原因: 使用默认参数可以使接口更加简洁, 减少重复代码. 它还可以使函数签名更加清晰, 并且更容易维护.
F.52: Prefer capturing by reference in lambdas that will be used locally, including passed to algorithms
- 翻译: 对于将在本地使用的 lambda 表达式(包括传递给算法的), 优先通过引用捕获.
- 原因: 引用捕获可以避免不必要的复制, 提高性能. 同时, 它使得 lambda 表达式可以直接访问外部变量, 而不需要显式传递这些变量.
F.53: Avoid capturing by reference in lambdas that will be used non-locally, including returned, stored on the heap, or passed to another thread
- 翻译: 避免在将用于非本地环境的 lambda 表达式中通过引用捕获(包括返回, 存储在堆上或传递给另一个线程).
- 原因: 引用捕获可能会导致悬空引用问题, 特别是在 lambda 表达式超出其原始作用域时. 这可能导致未定义行为, 因此在这种情况下应避免引用捕获.
F.54: When writing a lambda that captures this
or any class data member, don’t use [=]
default capture
- 翻译: 在编写捕获
this
或任何类数据成员的 lambda 表达式时, 不要使用[=]
默认捕获. - 原因: 默认捕获(
[=]
)会隐式地捕获所有局部变量和this
指针, 这可能导致意外的行为和更高的内存开销. 明确指定捕获列表可以提高代码的可读性和安全性.
F.55: Don’t use va_arg
arguments
- 翻译: 不要使用
va_arg
参数. - 原因:
va_arg
机制用于处理可变参数列表, 但它的使用容易出错且不安全. 现代 C++提供了更好的替代方案(如 variadic templates), 它们更安全且易于使用.
F.56: Avoid unnecessary condition nesting
- 翻译: 避免不必要的条件嵌套.
- 原因: 过度的条件嵌套会使代码变得难以阅读和理解, 并增加维护难度. 尽量简化逻辑结构, 使用提前返回(early return)或其他设计模式来减少嵌套层次.