码上智能

《Effective C++》是 Scott Meyers 的经典著作,总结了 55 条 C++ 编程核心准则。以下是核心建议的分类整理:


一、基础原则

  1. 视 C++ 为语言联邦
    • C++ 包含多范式:面向过程、面向对象、泛型编程、元编程等,需灵活切换思维方式。
  2. 优先使用 const, enum, inline 替代 #define
    • 减少预处理宏的副作用,增强类型安全和调试能力(例如 constexpr 优于宏常量)。
  3. 尽可能使用 const
    • const 修饰变量、函数参数、成员函数,明确语义并防止意外修改。

二、资源管理

  1. 手动管理资源的危险
    • 资源获取即初始化(RAII):用对象生命周期管理资源(如 std::unique_ptr 管理内存)。
  2. 析构函数释放所有资源
    • 确保析构函数释放对象持有的所有资源(内存、文件句柄、锁等)。
  3. 在构造和析构中避免调用虚函数
    • 此时虚函数机制未完全生效,可能导致未定义行为。

三、设计与继承

  1. 多用组合,少用继承
    • 组合(has-a 关系)比继承(is-a 关系)更灵活,降低耦合度。
  2. 为多态基类声明虚析构函数
    • 避免通过基类指针删除派生类对象时资源泄漏。
  3. 避免覆盖继承的 non-virtual 函数
    • 派生类覆盖非虚函数会导致行为不一致(静态绑定与动态绑定混淆)。

四、异常安全

  1. 使用析构函数防止资源泄漏
    • 将资源封装在对象中,利用栈展开(stack unwinding)确保异常时资源释放。
  2. 在构造函数中捕获异常
    • 若构造函数可能抛出异常,用 try-catch 块或工厂函数封装初始化逻辑。

五、模板与泛型

  1. 理解隐式接口和编译期多态
    • 模板的接口由表达式合法性隐式定义,多态通过模板实例化和函数重载实现。
  2. 使用 typename 声明依赖类型
    • 在模板中,用 typename 显式指明嵌套依赖类型(如 typename T::iterator)。

六、性能优化

  1. 警惕临时对象
    • 避免隐式类型转换(如单参数构造函数声明为 explicit)和返回对象副本。
  2. pass-by-reference-to-const 代替 pass-by-value
    • 减少拷贝开销,尤其对大型对象和继承体系。

第三次补充:条款 16–30


四、构造/析构/赋值操作

  1. operator= 返回 *this 的引用
    • 赋值操作符应返回左侧对象的引用(return *this;),以支持链式赋值(a = b = c;)。
  2. operator= 中处理自赋值
    • 检查 if (this == &rhs) return *this;,避免自赋值导致资源释放错误。
  3. 确保对象初始化
    • 所有成员变量在构造函数中初始化,优先使用成员初始化列表(效率高于构造函数内赋值)。

五、资源管理的高级技巧

  1. 区分拷贝和移动
    • 拷贝构造/赋值用于资源复制(深拷贝),移动构造/赋值(C++11)用于资源所有权转移(高效)。
  2. 用对象管理资源(RAII 核心)
    • 资源封装在对象中(如 std::shared_ptrstd::lock_guard),依赖析构函数自动释放。
  3. 资源管理类需设计拷贝行为
    • 禁止拷贝(= delete)、引用计数(shared_ptr)、深度拷贝或转移所有权(unique_ptr)。
  4. 在资源管理类中提供原始资源的访问接口
    • 通过 get() 方法或重载 operator->/operator* 安全暴露资源(如智能指针)。

六、设计与继承(续)

  1. 以非成员非友元函数增强封装性
    • 将工具函数声明为类的非成员函数,减少对私有成员的访问权限(例如 std::swap 特化)。
  2. 若所有参数需类型转换,用非成员函数
    • 运算符重载(如 operator+)若需隐式转换参数,应定义为非成员函数。
  3. 避免返回内部数据的句柄
    • 不返回成员对象的指针或引用,防止外部代码破坏封装性(如悬空指针)。

七、异常安全

  1. 保证异常安全的三种级别
    1. 基本保证:异常发生时程序状态合法;
    2. 强保证:操作要么完全成功,要么状态回滚;
    3. 不抛保证:承诺不抛出异常(如析构函数)。
  2. 使用 copy-and-swap 实现强异常安全
    • 先构造副本,再通过 swap 无异常地替换原对象(常用于赋值操作符)。

八、效率与灵活性

  1. 缓式评估(Lazy Evaluation)
    • 推迟计算直到必须执行(例如数据库查询拼接、大型对象拷贝)。
  2. 理解 inline 的局限
    • inline 函数需在头文件中定义,过度内联可能导致代码膨胀。
  3. 文件间的编译依赖最小化
    • 使用 前置声明Pimpl 模式(指针指向实现类),减少头文件包含。

六、设计与继承(续)

  1. 避免隐藏继承的名称
    • 派生类中的名称会覆盖基类同名符号,使用 using 声明或转交函数(forwarding function)显式暴露基类成员。
  2. 区分接口继承和实现继承
    • 纯虚函数(接口继承)、虚函数(接口+默认实现)、非虚函数(强制实现)。
  3. 绝不重新定义继承的 non-virtual 函数
    • 静态绑定导致派生类覆盖基类非虚函数时,基类指针调用仍执行基类版本。
  4. 绝不重新定义继承的默认参数
    • 默认参数是静态绑定的,虚函数的默认参数在基类和派生类中应一致。

七、模板与泛型编程

  1. 理解隐式接口和编译期多态
    • 模板的接口由操作合法性隐式定义,多态通过模板实例化和重载解析实现。
  2. 了解 typename 的双重含义
    • 在模板中,typename 用于声明嵌套依赖类型(如 typename T::iterator),非依赖类型无需使用。
  3. 学习处理模板化基类内的名称
    • 派生类模板中访问基类模板成员时,用 this->Base<T>:: 显式指明(避免编译器无法识别)。
  4. 将文件间的编译依赖降至最低
    • 使用前置声明(forward declaration)和指针封装(Pimpl 模式)减少头文件依赖。

八、异常安全

  1. 为异常安全而努力
    • 确保代码在异常发生时:1)不泄漏资源(基本保证);2)数据一致性(强保证);3)不中断(不抛保证)。
  2. 透彻了解 inline 的利弊
    • inline 可能增加代码膨胀,但能提升性能;避免在构造函数/析构函数中过度内联。
  3. 将文件间的编译依赖降至最低
    • 通过接口类(interface class)和工厂模式隔离实现细节,减少重新编译范围。

九、性能优化

  1. 考虑使用 lazy evaluation(缓式评估)
    • 延迟计算直到真正需要结果,例如矩阵运算中的临时对象合并。
  2. 谨记 80-20 法则
    • 优化前先用性能分析工具定位热点代码(20%的代码消耗80%的资源)。
  3. 活用 ammortized(分期摊还)优化
    • 通过批量操作降低单次开销(如 std::vector 的容量倍增策略)。
  4. 理解 std::movestd::forward 的差异
    • std::move 无条件转右值,std::forward 条件性保留值类别(用于完美转发)。

第四次补充:条款 46–55

十、高级技术

  1. 需要类型转换时定义非成员函数
    • 运算符重载(如 operator*)若需隐式转换,应声明为友元或非成员函数。
  2. 使用 traits classes 表现类型信息
    • 通过模板特化为类型添加编译期属性(如 std::iterator_traits)。
  3. 认识模板元编程(TMP)
    • 利用模板在编译期生成代码(如循环展开、条件分支),提升运行时效率。
  4. 了解 new-handler 的行为
    • 自定义内存分配失败时的处理函数(std::set_new_handler),实现分级分配或优雅降级。

十一、代码清晰性与可维护性

  1. 让接口容易被正确使用,不易被误用
    • 限制参数类型(如用 enum 替代 int)、提供一致性接口、封装资源管理。
  2. 避免写出未定义行为
    • 如解引用空指针、数组越界、违反 strict aliasing 规则等。
  3. 关注编译器警告
    • 将警告级别调到最高,并对待警告如错误(如 -Wall -Werror)。

十二、其他关键实践

  1. 熟悉标准库(尤其是 STL)
    • 掌握容器、算法、迭代器的正确使用(如 std::vector 优先于数组)。
  2. 警惕对“效率”的过度追求
    • 优先保证正确性、可维护性,仅在性能分析后优化热点代码。
  3. 熟悉 Boost 等高质量库
    • 利用社区资源(如 Boost.SmartPtr, Boost.Asio)避免重复造轮子。

总结

这些条款涵盖了 C++ 的核心设计哲学、资源管理、性能优化及工程实践。建议结合实际项目,逐步应用这些准则。如果需要更详细的解释或示例,可以针对具体条款深入讨论!