以下是阅读 Effective C++ 过程中整理的要点笔记,按条款顺序记录关键结论,便于日后查阅。

对象构造与拷贝控制

  • 若要强制禁止赋值或拷贝构造,可将 copy 构造函数和 copy assignment 操作符声明为 private,并且可以不提供实现。如果要防止子类拷贝,可以让子类以 private 继承一个已经禁止拷贝的父类。
  • 在没有特殊要求时,参数尽量使用 const。
  • 带多态性质的基类,应该声明一个 virtual 析构函数。只要一个类拥有任何 virtual 函数,就应该拥有一个 virtual 析构函数。
  • 如果类的设计目的不是作为基类使用,或者不需要具备多态性,那么就不应该声明 virtual 析构函数。
  • 设计抽象类时,如果找不到一个合适的纯虚函数,可以考虑把析构函数声明为纯虚函数。

赋值与拷贝函数

  • operator= 返回 *this 的目的,是为了方便连续赋值操作,比如 a=b=c;。
  • operator= 中必须处理自我赋值的情况,例如 a=a;。
  • copy 函数应当确保对象内所有成员变量都被安全复制,并且基类部分也要一并复制。
  • 当多个 copy 函数的公共部分过多时,可以尝试把共用逻辑提取到一个 private 函数中。

资源管理

  • 为防止资源泄露,尽量使用 RAII 对象:在构造函数中获得资源,在析构函数中释放资源。
  • new[] 必须对应 delete[],new 必须对应 delete。
  • 抛出异常前,要确认资源已经被安全释放。

接口设计

  • 导入新类型,可以防止接口被乱用(参见 P79)。
  • 好的接口应该很容易被正确使用,不容易被误用。你应该在所有接口的设计上努力达成这两点性质。
  • 促进"正确使用"的办法,包括保持接口的统一性和一致性,以及与内置类型的行为兼容。
  • "阻止误用"的办法,包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任。

类与封装

  • class 的设计,本质上就是 type 的设计。
  • 传递参数时,如果参数包含对象,尽量使用 reference 传递,可以避免大量没必要的拷贝等工作。尤其是在多态的场景下,传值会导致对象被切割;不过 STL 风格的传值通常更合适。
  • 将成员变量声明为 public 或 protected 都会破坏封装性。一旦成员变量被改变,依赖它的代码将受到不可预知的破坏。
  • protected 并不比 public 更有封装性,特别是在多态的场景下。
  • 切记把成员变量声明为 private。这样可以赋予客户访问数据时的一致性,可以做细微的访问控制,可以施加各种约束保证,并为 class 作者保留充分的实现弹性。

类型转换与编译期优化

  • 隐式类型转换可以发生在基本类型和自定义类型之间,只要自定义类型提供了对应的构造函数。
  • 尽可能延后变量的定义,因为提前定义可能白白承担构造和析构的代价。延后定义既能提高程序清晰度,又能改善运行效率。
  • 将 inline 谨慎使用,把它限制在小型、经常被调用的函数上。这样既能让调试和二进制升级更容易,又能把潜在的代码膨胀问题降到最小,同时把程序速度提升最大化。

编译依存性最小化

  • pImpl 手法参见 P145 的相关条款。
  • 支持"编译依存性最小化"的一般构想是:依赖于声明,不要依赖定义。基于这一构想的两个手段,是 handle classes 和 interface classes。
  • 程序库头文件应该以"完全且仅有声明"的形式存在,这种做法不论是否涉及 template 都同样适用。

继承与名称查找

  • public 继承表达的是 is-a 关系:子类对象也是一个父类对象。比如"黄种人"是"人",所以"黄种人"可以继承自"人"。
  • derived class 内的名称会遮挡 base class 内的同名名称,而在 public 继承下,从来没有人希望出现这种遮挡。
  • 为了让被遮挡的名称重见天日,可以使用 using 声明式,或者写一个转交函数。