C++核心指导原则: 类和类层次结构
C++ Core Guidelines 整理目录
类和类层次结构
主要规则
C.1: Organize related data into structures (struct
s or class
es)
- 翻译: 将相关数据组织成结构(
struct
s 或class
es) - 原因: 通过将相关的数据成员组合在一起, 可以提高代码的可读性和维护性. 这有助于清晰地表示这些数据之间的关系, 并便于管理.
- 示例:
- 提示:
- 一个简单类(没有虚函数)没有任何空间或者时间额外开销.
- 从语言的角度上看,
class
和struct
的区别仅在于默认的访问级别.
C.2: Use class
if the class has an invariant; use struct
if the data members can vary independently
- 翻译: 如果类具有不变量则使用
class
; 若数据成员可以独立变化则使用struct
- 原因: 类(
class
)通常用于封装状态和行为, 并确保对象在其生命周期内的某个时刻满足特定条件(即不变量). 而结构体(struct
)更适合于简单的数据聚合, 其中的数据成员不需要维持特定的关系或状态.
C.3: Represent the distinction between an interface and an implementation using a class
- 翻译: 使用类来表示接口与实现之间的区别
- 原因: 通过使用类将接口与实现分离, 可以隐藏实现细节, 只暴露必要的接口给用户, 从而增强程序的灵活性和可维护性.
C.4: Make a function a member only if it needs direct access to the representation of a class
翻译: 只有当函数需要直接访问类的表示时, 才将其作为成员函数
原因: 减少成员函数的数量可以降低类的复杂度, 提高其可维护性. 非成员函数可以在不增加类内部复杂性的前提下扩展类的功能.
示例:
辅助函数没有必要访问类的内部细节.
C.5: Place helper functions in the same namespace as the class they support
- 翻译: 将辅助函数放在支持它们的类所在的命名空间中
- 原因: 这样做可以使辅助函数更容易找到和使用, 同时保持代码的组织性和清晰度, 避免命名冲突.
C.7: Don’t define a class or enum and declare a variable of its type in the same statement
翻译: 不要在同一个语句中定义一个类或枚举并声明一个该类型的变量
原因: 这种做法可能会导致代码难以阅读和理解, 同时也可能引起编译器解析上的混淆, 特别是在复杂的类型定义场景中.
示例:
C.8: Use class
rather than struct
if any member is non-public
- 翻译: 如果任何成员是非公共的, 则使用
class
而非struct
- 原因: 结构体默认是公开的, 而类提供了一个更自然的方式来控制访问级别, 使得设计意图更加明确.
C.9: Minimize exposure of members
- 翻译: 最小化成员的暴露
- 原因: 减少对类成员的直接访问可以增强封装性, 减少外部依赖, 从而使系统更加灵活和易于维护. 这也有助于防止误用和潜在的错误.
C.concrete: 具体类型
C.10: Prefer concrete types over class hierarchies
- 翻译: 优先选择具体类型而不是类层次结构
- 原因: 具体类型通常比类层次结构更简单, 更高效. 除非确实需要多态行为, 否则应尽量避免复杂的继承关系, 因为它们会增加代码复杂性和维护成本.
C.11: Make concrete types regular
- 翻译: 使具体类型具有常规性
- 原因: 具有常规性的类型(如支持复制, 赋值和比较操作)更容易使用和理解. 遵循常规类型的模式可以提高代码的一致性和可预测性, 从而简化开发和调试过程.
C.12: Don’t make data members const
or references in a copyable or movable type
- 翻译: 在可拷贝或可移动的类型中, 不要将数据成员定义为 const 或引用
- 原因: 如果一个类型是可拷贝或可移动的, 那么它的数据成员不应是常量或引用. 这是因为常量和引用限制了对象的状态修改能力, 使得拷贝和移动操作难以实现或无法正常工作. 这会影响类型的设计灵活性和可用性.
C.ctor: 构造, 赋值以及析构
这些函数控制对象的生命周期: 创建, 拷贝, 移动和销毁.
下面是 默认操作:
- 默认构造函数:
X()
- 拷贝构造函数:
X(const X&)
- 拷贝赋值函数:
operator=(const X&)
- 移动构造函数:
X(X&&)
- 移动赋值函数:
operator=(X&&)
- 析构函数:
~X()
编译器会默认生成这些函数, 不过这个行为可以被抑制.
C.20: If you can avoid defining any default operations, do
- 翻译: 如果可以避免定义任何默认操作, 请这样做.
- 原因: 默认构造函数, 拷贝构造函数, 赋值运算符, 移动构造函数, 移动赋值运算符和析构函数由编译器自动生成. 如果这些默认操作能满足需求, 则不需要手动定义它们. 这可以减少代码量, 并降低出错的可能性.
C.21: If you define or =delete
any copy, move, or destructor function, define or =delete
them all
- 翻译: 如果你定义或删除了任何拷贝, 移动或析构函数, 请定义或删除所有相关函数.
- 原因: 这是为了确保类的行为一致性和完整性. 如果你手动定义或删除了其中一个特殊成员函数(如拷贝构造函数), 那么你也应该考虑其他相关函数(如拷贝赋值运算符, 移动构造函数, 移动赋值运算符和析构函数). 否则, 可能会导致意外行为或资源管理问题.
C.22: Make default operations consistent
- 翻译: 使默认操作保持一致性.
- 原因: 默认操作(如拷贝构造函数, 赋值运算符等)应遵循一致的设计原则. 例如, 如果一个类支持拷贝操作, 那么它的拷贝构造函数和拷贝赋值运算符应该具有相似的行为和实现逻辑. 这样可以提高代码的可读性和维护性, 并减少潜在的错误.
析构函数的规则:
C.30: Define a destructor if a class needs an explicit action at object destruction
- 翻译: 如果类在对象销毁时需要执行显式操作, 请定义一个析构函数.
- 原因: 当对象被销毁时, 如果需要释放资源(如内存, 文件句柄等), 则应定义一个析构函数来执行这些操作. 这可以确保资源被正确释放, 避免内存泄漏或其他资源管理问题.
C.31: All resources acquired by a class must be released by the class’s destructor
- 翻译: 类获取的所有资源必须由类的析构函数释放.
- 原因: 为了防止资源泄漏, 所有通过类获取的资源(如动态分配的内存, 打开的文件等)应在类的析构函数中进行释放. 这有助于确保资源管理的一致性和可靠性.
C.32: If a class has a raw pointer (T*
) or reference (T&
), consider whether it might be owning
- 翻译: 如果一个类包含原始指针(
T*
)或引用(T&
), 考虑它是否可能是拥有者. - 原因: 原始指针或引用可能表示对资源的所有权. 如果是这种情况, 需要明确如何管理该资源的生命周期, 并确保在对象销毁时正确释放资源.
C.33: If a class has an owning pointer member, define a destructor
- 翻译: 如果一个类有一个拥有者指针成员, 则应定义一个析构函数.
- 原因: 当类包含一个拥有者指针成员时, 意味着该指针负责管理其所指向的资源. 因此, 需要定义一个析构函数来确保在对象销毁时正确释放该资源.
C.35: A base class destructor should be either public and virtual, or protected and non-virtual
- 翻译: 基类的析构函数应该是公共且虚拟的, 或者保护且非虚拟的.
- 原因: 如果基类的析构函数是公共的, 则应将其声明为虚拟的, 以确保派生类对象通过基类指针删除时能够正确调用派生类的析构函数. 如果基类的析构函数是受保护的, 则应将其声明为非虚拟的, 因为在这种情况下不会通过基类指针删除对象.
C.36: A destructor must not fail
- 翻译: 析构函数不能失败.
- 原因: 析构函数不应抛出异常, 因为它们通常在栈展开过程中被调用. 如果析构函数抛出异常, 可能会导致程序崩溃或未定义行为. 因此, 应设计析构函数以确保其总是成功完成.
C.37: Make destructors noexcept
- 翻译: 将析构函数声明为 noexcept.
- 原因: 标记析构函数为
noexcept
可以提高代码的安全性和性能. 许多标准库组件依赖于析构函数不抛出异常这一假设, 标记为noexcept
可以确保这一点, 并允许编译器进行优化.
构造函数的规则:
C.40: Define a constructor if a class has an invariant
- 翻译: 如果类具有不变量, 则定义一个构造函数.
- 原因: 构造函数用于确保对象在创建时满足其不变量. 这有助于确保对象始终处于有效状态, 并简化后续操作.
C.41: A constructor should create a fully initialized object
- 翻译: 构造函数应创建一个完全初始化的对象.
- 原因: 构造函数应在返回之前确保对象的所有成员都已正确初始化. 这可以避免未初始化成员导致的潜在错误.
C.42: If a constructor cannot construct a valid object, throw an exception
- 翻译: 如果构造函数无法构造有效的对象, 则抛出异常.
- 原因: 当构造函数无法创建有效的对象时, 应该通过抛出异常来通知调用者. 这样可以防止程序继续使用无效对象.
C.43: Ensure that a copyable class has a default constructor
- 翻译: 确保可拷贝的类有一个默认构造函数.
- 原因: 可拷贝的类需要能够被默认构造, 以便在容器中存储或进行其他操作时不会出现问题.
C.44: Prefer default constructors to be simple and non-throwing
- 翻译: 优先使默认构造函数简单且不抛出异常.
- 原因: 默认构造函数应尽量保持简单, 并避免抛出异常, 以提高代码的安全性和效率.
C.45: Don’t define a default constructor that only initializes data members; use member initializers instead
翻译: 不要定义仅初始化数据成员的默认构造函数; 使用成员初始化器代替.
原因: 使用成员初始化器可以在声明成员变量时直接初始化它们, 而不是在构造函数中重复初始化代码, 从而减少冗余并提高代码清晰度.
示例:
C.46: By default, declare single-argument constructors explicit
- 翻译: 默认情况下, 将单参数构造函数声明为
explicit
. - 原因: 将单参数构造函数声明为
explicit
可以防止隐式转换, 从而避免意外的类型转换问题.
C.47: Define and initialize data members in the order of member declaration
- 翻译: 按照成员声明的顺序定义和初始化数据成员.
- 原因: 数据成员的初始化顺序应与它们在类中声明的顺序一致, 以避免由于初始化顺序不同而导致的未定义行为.
C.48: Prefer default member initializers to member initializers in constructors for constant initializers
- 翻译: 对于常量初始化器, 优先使用默认成员初始化器而不是构造函数中的成员初始化器.
- 原因: 使用默认成员初始化器可以使代码更加简洁和易读, 并且可以确保成员变量在任何构造函数中都被正确初始化.
C.49: Prefer initialization to assignment in constructors
- 翻译: 在构造函数中优先使用初始化而不是赋值.
- 原因: 使用初始化(如成员初始化列表)比在构造函数体内进行赋值更高效, 因为它避免了不必要的临时对象创建和复制.
C.50: Use a factory function if you need “virtual behavior” during initialization
- 翻译: 如果在初始化期间需要"虚拟行为", 请使用工厂函数.
- 原因: 工厂函数可以在对象创建时根据运行时条件选择适当的子类, 从而实现多态性. 这在基类构造函数中无法直接调用虚函数的情况下特别有用.
C.51: Use delegating constructors to represent common actions for all constructors of a class
- 翻译: 使用委托构造函数表示类所有构造函数的共同操作.
- 原因: 委托构造函数可以减少代码重复, 并确保所有构造函数执行相同的初始化逻辑. 这提高了代码的可维护性和一致性.
C.52: Use inheriting constructors to import constructors into a derived class that does not need further explicit initialization
- 翻译: 使用继承构造函数将基类的构造函数导入不需要进一步显式初始化的派生类.
- 原因: 继承构造函数可以避免在派生类中重新定义基类的构造函数, 从而减少代码冗余, 并确保派生类的构造函数与基类的一致性.
拷贝和移动规则:
C.60: Make copy assignment non-virtual
, take the parameter by const&
, and return by non-const&
- 翻译: 使拷贝赋值运算符非虚拟, 参数为
const&
, 返回类型为non-const&
. - 原因: 拷贝赋值运算符应为非虚拟函数, 并通过引用传递参数以提高效率. 返回类型为
non-const&
允许链式调用(如a = b = c;
).
C.61: A copy operation should copy
- 翻译: 拷贝操作应该执行真正的拷贝.
- 原因: 拷贝构造函数和拷贝赋值运算符应确保对象的所有成员都被正确拷贝, 以避免浅拷贝导致的问题.
C.62: Make copy assignment safe for self-assignment
- 翻译: 使拷贝赋值运算符对自赋值安全.
- 原因: 自赋值是指将一个对象赋值给自己(如
a = a;
). 拷贝赋值运算符应能够处理这种情况, 以防止资源泄漏或其他未定义行为.
C.63: Make move assignment non-virtual
, take the parameter by &&
, and return by non-const&
- 翻译: 使移动赋值运算符非虚拟, 参数为右值引用(
&&
), 返回类型为non-const&
. - 原因: 移动赋值运算符应为非虚拟函数, 并通过右值引用传递参数以提高效率. 返回类型为
non-const&
允许链式调用.
C.64: A move operation should move and leave its source in a valid state
- 翻译: 移动操作应该移动资源并使源对象处于有效状态.
- 原因: 移动操作应将资源从源对象转移到目标对象, 并确保源对象在操作后仍处于有效状态(尽管可能是空或无效的状态).
C.65: Make move assignment safe for self-assignment
- 翻译: 使移动赋值运算符对自赋值安全.
- 原因: 自赋值是指将一个对象赋值给自己(如
a = std::move(a);
). 移动赋值运算符应能够处理这种情况, 以防止资源泄漏或其他未定义行为.
C.66: Make move operations noexcept
- 翻译: 将移动操作标记为
noexcept
. - 原因: 标记移动操作为
noexcept
可以提高代码的安全性和性能. 许多标准库组件依赖于移动操作不抛出异常这一假设, 因此标记为noexcept
是良好的实践.
C.67: A polymorphic class should suppress public copy/move
- 翻译: 多态类应禁止公开的拷贝/移动操作.
- 原因: 对于多态类, 公开的拷贝或移动操作可能导致对象切片问题(即派生类对象被当作基类对象使用). 为了避免这种问题, 通常应禁用这些操作.
其他默认操作规则:
C.80: Use =default
if you have to be explicit about using the default semantics
- 翻译: 如果你必须明确使用默认语义, 请使用
=default
. - 原因: 使用
=default
可以明确表示你想使用编译器生成的默认实现. 这有助于提高代码的可读性和维护性, 同时避免手动编写可能出错的代码.
C.81: Use =delete
when you want to disable default behavior (without wanting an alternative)
- 翻译: 当你想禁用默认行为(且不需要替代方案)时, 请使用
=delete
. - 原因: 使用
=delete
可以显式禁止某些操作(如拷贝构造函数或赋值运算符), 以防止不希望的行为发生, 并使意图更加清晰.
C.82: Don’t call virtual functions in constructors and destructors
- 翻译: 不要在构造函数和析构函数中调用虚函数.
- 原因: 在构造函数或析构函数中调用虚函数可能会导致未定义行为, 因为派生类对象的部分可能尚未构造或已经被销毁. 这会引发难以调试的问题.
C.83: For value-like types, consider providing a noexcept
swap function
- 翻译: 对于类似值类型的类型, 考虑提供一个
noexcept
的交换函数. - 原因: 提供一个
noexcept
的交换函数可以提高性能, 并允许标准库容器和算法更高效地使用该类型. 这在实现自定义类型时尤为重要.
C.84: A swap
must not fail
- 翻译: 交换操作不能失败.
- 原因: 交换操作应确保不会抛出异常, 以避免资源泄漏或其他未定义行为. 通常通过仔细设计来确保交换操作是无抛出的.
C.85: Make swap
noexcept
- 翻译: 将交换函数标记为
noexcept
. - 原因: 标记交换函数为
noexcept
可以提高代码的安全性和性能. 许多标准库组件依赖于交换函数不抛出异常这一假设, 因此标记为noexcept
是良好的实践.
C.86: Make ==
symmetric with respect of operand types and noexcept
- 翻译: 使相等运算符(
==
)对操作数类型对称且noexcept
. - 原因: 相等运算符应确保其行为对操作数类型是对称的, 并且不应抛出异常. 这有助于提高代码的可靠性和可预测性.
C.87: Beware of ==
on base classes
- 翻译: 注意基类上的相等运算符(
==
). - 原因: 在基类上直接使用相等运算符可能导致切片问题, 即派生类特有的成员未被比较. 应小心处理这种情况, 确保所有相关成员都被正确比较.
C.89: Make a hash
noexcept
- 翻译: 将哈希函数标记为
noexcept
. - 原因: 哈希函数应确保不会抛出异常, 以避免在使用标准库容器(如
unordered_map
)时出现问题. 标记为noexcept
可以提高性能并增强可靠性.
C.90: Rely on constructors and assignment operators, not memset and memcpy
- 翻译: 依靠构造函数和赋值运算符, 而不是
memset
和memcpy
. - 原因: 使用构造函数和赋值运算符可以确保对象的状态被正确初始化和管理, 而
memset
和memcpy
可能会绕过这些机制, 导致未定义行为或资源管理问题.
C.con: 容器和其他资源句柄
C.100: Follow the STL when defining a container
- 翻译: 在定义容器时遵循 STL.
- 原因: 标准模板库(STL)提供了丰富的容器设计模式和最佳实践. 遵循这些模式可以使你的自定义容器更符合用户的预期, 并与其他标准库组件更好地集成.
C.101: Give a container value semantics
- 翻译: 给容器赋予值语义.
- 原因: 值语义意味着容器可以像内置类型一样进行复制, 赋值和比较操作. 这使得容器更容易使用和理解, 并且与 STL 容器的行为保持一致.
C.102: Give a container move operations
- 翻译: 为容器提供移动操作.
- 原因: 移动操作可以提高性能, 特别是在处理大型数据结构时. 通过实现移动构造函数和移动赋值运算符, 可以避免不必要的复制操作, 从而提高效率.
C.103: Give a container an initializer list constructor
- 翻译: 为容器提供初始化列表构造函数.
- 原因: 初始化列表构造函数允许用户在创建容器时直接使用花括号语法初始化其内容. 这不仅提高了代码的可读性, 还使容器的使用更加方便.
C.104: Give a container a default constructor that sets it to empty
- 翻译: 为容器提供一个将其设置为空的默认构造函数.
- 原因: 默认构造函数应将容器初始化为空状态, 以确保容器在创建后处于有效状态. 这有助于简化容器的使用, 并减少潜在的错误.
C.109: If a resource handle has pointer semantics, provide *
and ->
- 翻译: 如果资源句柄具有指针语义, 请提供
*
和->
操作符. - 原因: 提供指针操作符(如
*
和->
)可以使资源句柄的行为类似于普通指针, 从而使代码更具直观性和一致性. 这对于管理动态分配的资源特别有用.
C.hier: 类层次结构 (OOP)
C.120: Use class hierarchies to represent concepts with inherent hierarchical structure (only)
- 翻译: 使用类层次结构来表示具有内在层级结构的概念(仅此而已).
- 原因: 类层次结构非常适合表达那些本质上具有层级关系的概念. 例如, 在图形库中, 不同类型的图形(如圆形, 矩形等)可以继承自一个通用的"图形"基类. 这种做法有助于代码复用和扩展性, 但应避免滥用类层次结构来处理不具有天然层级关系的概念.
C.121: If a base class is used as an interface, make it a pure abstract class
- 翻译: 如果基类用作接口, 请将其设为纯抽象类.
- 原因: 当一个基类被设计为接口供其他类实现时, 应该声明为纯抽象类. 这样做强制要求派生类必须提供具体实现, 确保了接口的一致性和完整性. 这也有助于在设计阶段就明确哪些部分是预期由派生类提供的行为.
C.122: Use abstract classes as interfaces when complete separation of interface and implementation is needed
- 翻译: 在需要完全分离接口和实现时, 使用抽象类作为接口.
- 原因: 抽象类在需要将接口与实现彻底分离的情况下非常有用. 通过这种方式, 你可以定义一组方法而无需提供它们的具体实现, 让派生类根据具体需求去实现这些方法. 这样不仅提高了灵活性, 还允许对实现细节进行更改而不影响到依赖该接口的其他部分代码.
C.126: An abstract class typically doesn’t need a user-written constructor
- 翻译: 抽象类通常不需要用户编写的构造函数.
- 原因: 抽象类主要用于定义接口, 通常不需要自定义构造函数.
C.127: A class with a virtual function should have a virtual or protected destructor
- 翻译: 包含虚函数的类应该有一个虚或受保护的析构函数.
- 原因: 确保正确的析构顺序, 避免未定义行为.
C.128: Virtual functions should specify exactly one of virtual
, override
, or final
- 翻译: 虚函数应明确指定
virtual
,override
或final
之一. - 原因: 提供明确的意图, 帮助编译器检查.
C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance
- 翻译: 在设计类层次结构时, 区分实现继承和接口继承.
- 原因: 保持设计清晰, 便于维护.
C.130: For making deep copies of polymorphic classes prefer a virtual clone
function instead of public copy construction/assignment
- 翻译: 对于多态类的深拷贝, 建议使用虚拟的
clone
函数. - 原因: 确保根据实际类型创建正确副本.
C.131: Avoid trivial getters and setters
- 翻译: 避免使用无意义的 getter 和 setter.
- 原因: 直接访问通常更快且更简洁.
C.132: Don’t make a function virtual
without reason
- 翻译: 不要无缘无故地将函数声明为虚函数.
- 原因: 避免不必要的性能开销.
C.133: Avoid protected
data
- 翻译: 避免使用受保护的数据成员.
- 原因: 维护封装性, 减少数据被意外修改的风险.
C.134: Ensure all non-const
data members have the same access level
- 翻译: 确保所有非
const
数据成员具有相同的访问级别. - 原因: 简化设计和维护.
C.135: Use multiple inheritance to represent multiple distinct interfaces
- 翻译: 使用多重继承来表示多个不同的接口.
- 原因: 增强灵活性, 但需谨慎使用.
C.136: Use multiple inheritance to represent the union of implementation attributes
- 翻译: 使用多重继承来表示实现属性的联合.
- 原因: 组合多个独立的实现特性.
C.137: Use virtual
bases to avoid overly general base classes
- 翻译: 使用虚基类以避免过于通用的基类.
- 原因: 防止重复继承.
C.138: Create an overload set for a derived class and its bases with using
- 翻译: 使用
using
为派生类及其基类创建重载集. - 原因: 实现无缝的重载和扩展功能.
C.139: Use final
on classes sparingly
- 翻译: 尽量少用
final
修饰类. - 原因: 限制灵活性, 仅在必要时使用.
C.140: Do not provide different default arguments for a virtual function and an overrider
- 翻译: 不要为虚函数和其重写者提供不同的默认参数.
- 原因: 避免意想不到的行为.
C.145: Access polymorphic objects through pointers and references
- 翻译: 通过指针和引用访问多态对象.
- 原因: 使用指针或引用来处理多态对象可以实现运行时动态绑定, 允许调用派生类重写的虚函数. 这有助于编写更加灵活和可扩展的代码.
C.146: Use dynamic_cast
where class hierarchy navigation is unavoidable
- 翻译: 在类层次结构导航不可避免的情况下使用
dynamic_cast
. - 原因: 当你需要安全地在类层次结构中向下转换类型时,
dynamic_cast
提供了类型检查机制, 确保转换的安全性. 如果转换失败, 它会返回nullptr
(对于指针)或者抛出std::bad_cast
异常(对于引用), 避免了潜在的错误.
C.147: Use dynamic_cast
to a reference type when failure to find the required class is considered an error
- 翻译: 当未能找到所需类被视为错误时, 使用
dynamic_cast
到引用类型. - 原因: 使用
dynamic_cast
进行引用类型的转换, 可以在转换失败时抛出异常, 这样可以在程序中更早地捕获并处理错误情况, 提高程序的健壮性和可靠性.
C.148: Use dynamic_cast
to a pointer type when failure to find the required class is considered a valid alternative
- 翻译: 当未能找到所需类被视为一个有效的替代方案时, 使用
dynamic_cast
到指针类型. - 原因: 指针类型的
dynamic_cast
在转换失败时返回nullptr
, 这种情况下不需要处理异常, 适合用于那些找不到目标类型也是一种预期行为的场景.
C.149: Use unique_ptr
or shared_ptr
to avoid forgetting to delete
objects created using new
- 翻译: 使用
unique_ptr
或shared_ptr
以避免忘记删除使用new
创建的对象. - 原因: 智能指针(如
unique_ptr
和shared_ptr
)自动管理动态分配对象的生命周期, 减少内存泄漏的风险, 并简化资源管理逻辑.
C.150: Use make_unique()
to construct objects owned by unique_ptr
s
- 翻译: 使用
make_unique()
构造由unique_ptrs
拥有的对象. - 原因:
make_unique
是一个便捷且安全的方法来创建unique_ptr
实例, 它减少了显式使用new
的需求, 同时提高了代码的可读性和安全性.
C.151: Use make_shared()
to construct objects owned by shared_ptr
s
- 翻译: 使用
make_shared()
构造由shared_ptrs
拥有的对象. - 原因:
make_shared
不仅简洁, 还可能比直接使用new
与shared_ptr
构造函数结合更高效, 因为它能够在一个内存块中分配控制块和对象数据, 优化内存布局和性能.
C.152: Never assign a pointer to an array of derived class objects to a pointer to its base
- 翻译: 切勿将指向派生类对象数组的指针赋值给基类类型的指针.
- 原因: 这种操作可能导致数组越界访问的问题, 因为派生类对象的实际大小可能大于基类对象的大小, 导致后续元素的访问位置偏移不正确, 容易引发未定义行为.
C.153: Prefer virtual function to casting
- 翻译: 优先使用虚函数而不是强制类型转换.
- 原因: 虚函数支持运行时多态性, 使得代码更具灵活性和可维护性, 而强制类型转换可能会破坏封装性, 并增加代码的复杂度和出错的可能性.
C.over: Overloading and overloaded operators
C.160: Define operators primarily to mimic conventional usage
- 翻译: 主要定义运算符以模仿传统用法.
- 原因: 定义运算符时应遵循其在其他上下文中的常规使用方式, 以便代码更加直观易懂. 这有助于减少学习曲线并提高代码的可读性和维护性.
C.161: Use non-member functions for symmetric operators
- 翻译: 对称运算符使用非成员函数.
- 原因: 非成员函数允许运算符两边的操作数类型不同, 从而支持更灵活的操作. 例如, 对于
operator+
, 这样做可以使得两个不同类型对象之间的加法操作成为可能, 同时保持运算符的对称性.
C.162: Overload operations that are roughly equivalent
- 翻译: 重载大致等价的操作.
- 原因: 如果不同的操作具有相似的功能或意图, 可以通过重载使它们共享相同的实现逻辑. 这样不仅可以减少重复代码, 还能提高代码的一致性和可维护性.
C.163: Overload only for operations that are roughly equivalent
- 翻译: 仅对大致等价的操作进行重载.
- 原因: 过度重载可能导致混淆和误解, 因此只应在操作之间存在明显相似性的情况下进行重载. 确保每个重载版本的行为都是预期且合理的.
C.164: Avoid implicit conversion operators
- 翻译: 避免隐式转换操作符.
- 原因: 隐式转换可能会导致意外的行为和难以调试的错误. 通过避免隐式转换, 可以增强代码的安全性和可预测性, 同时也提高了代码的可读性.
C.165: Use using
for customization points
- 翻译: 使用
using
作为自定义点. - 原因:
using
声明可以帮助你在命名空间中引入特定的名字, 这对于扩展标准库或第三方库的功能特别有用. 它提供了一种清晰的方式来添加或修改现有接口的功能.
C.166: Overload unary &
only as part of a system of smart pointers and references
- 翻译: 只有作为智能指针和引用系统的一部分时才重载一元
&
. - 原因: 一元
&
通常用于获取变量的地址, 如果随意重载可能会破坏这种基本功能. 将其限制在智能指针和引用系统内可以确保行为的一致性和安全性.
C.167: Use an operator for an operation with its conventional meaning
- 翻译: 使用运算符表示具有传统意义的操作.
- 原因: 遵循运算符的传统含义可以使代码更具可读性和直观性, 避免不必要的混淆. 例如, 使用
+
来表示加法, 而不是一些完全不同的操作.
C.168: Define overloaded operators in the namespace of their operands
- 翻译: 在操作数所属的命名空间中定义重载运算符.
- 原因: 将重载运算符放在正确的命名空间中可以确保它们能够正确地与相关类型一起工作, 同时也有助于避免命名冲突和其他潜在问题.
C.170: If you feel like overloading a lambda, use a generic lambda
- 翻译: 如果你觉得需要重载一个 lambda 表达式, 请使用泛型 lambda.
- 原因: 泛型 lambda 允许你编写适用于多种类型的代码, 而无需为每种类型单独编写重载版本. 这种方式不仅简洁, 还增强了代码的灵活性和复用性.
C.union: Unions
C.180: Use union
s to save Memory
- 翻译: 使用联合体(unions)来节省内存.
- 原因: 允许在同一内存位置存储不同类型的数据成员, 有效减少内存使用.
C.181: Avoid “naked” union
s
- 翻译: 避免"裸"联合体.
- 原因: 由于缺乏管理机制, 容易导致未定义行为, 建议使用带安全措施的联合体实现.
C.182: Use anonymous union
s to implement tagged unions
- 翻译: 使用匿名联合体实现标记联合体(tagged unions).
- 原因: 结合标签使用匿名联合体, 可以安全地存储多种类型之一的数据.
C.183: Don’t use a union
for type punning
- 翻译: 不要使用联合体进行类型转换(type punning).
- 原因: 可能导致未定义行为, 应使用更安全的方法进行类型转换.
Tags: