Effective C++ Note

《Effective C++》 一些记录(更新中)。

条款 03: 尽可能的使用 const

对于 non-const 版本的函数可以采取调用 const 版本的函数的方法来实现。

1
2
3
4
5
6
7
8
9
class TextBlock {
public:
const char &operator[](int i) const { return text[i]; }
char &operator[](int i) { return const_cast<char &>(static_cast<const TextBlock &>(*this)[i]); }
TextBlock(std::string s) : text(s) {}

private:
std::string text;
};

反向做法是不应该的。const 成员函数承诺绝不改变其对象的逻辑状态,但是 non-const 成员函数没有承诺,在 const 成员函数内调用 non-const 成员函数是有风险的。

请记住:

  • 将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加于任何作用域的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施 bitwise constness,但编写程序时应该使用“概念上的常量性” (conceptual constness)。
  • constnon-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。

条款 04: 确定对象被使用前已先被初始化

请记住:

  • 为内置型对象进行手工初始化,因为 C++ 不保证初始化它们。
  • 构造函数最好使用成员初值列 (member initialization list),而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和他们在 class 中声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。

条款 05: 了解 C++ 默默编写并调用哪些函数

对于拷贝构造函数和拷贝赋值运算符,编译器创建的版本只是单纯地将来源对象的每一个非静态成员变量拷贝到目标对象。

请记住:

  • 编译器可以暗自为 class 创建默认构造函数、拷贝构造函数、拷贝赋值运算符,以及析构函数。

条款 06: 若不想使用编译器自动生成的函数,就该明确拒绝(可能已过时)

为驳回编译器自动生成的函数,可以将相应的成员函数声明为 private 并且不予实现。
在 C++11 中可以将其置为 delete 来实现。

条款 07: 为多态基类声明 virtual 析构函数

请记住:

  • 带多态性质的基类应该声明一个虚析构函数。如果类带有任何虚函数,它就应该拥有一个虚析构函数。
  • 一个类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明虚析构函数。

条款 08: 别让异常逃离析构函数

  • todo

条款 09: 绝不在构造和析构过程中调用虚函数

在基类构造和析构的期间调用的 virtual 函数不可下降至派生类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct Super {
Super() { test(); }
virtual ~Super() { test(); }
virtual void test() { std::cerr << "super" << std::endl; }
};

struct Derived : Super {
void test() override { std::cerr << "derived" << std::endl; }
};

int main() {
Super *p = new Derived;
p->test();
delete p;
}

输出:

1
2
3
super
derived
super

条款 10: 令 operator= 返回一个 *this 引用

条款 11: 在 operator= 中处理自我赋值

条款 12: 复制对象时勿忘其每一个成分

  • 复制函数应该确保复制“对象内的所有成员变量”及“所有基类成分”。
  • 不要尝试以某个复制函数实现另一个复制函数。应该将共同机能放进第三个函数中,并由两个复制函数共同调用。

条款 32: 确定你的 public 继承塑膜出 is-a 关系

  • public 继承意味着 is-a。适用于基类身上的每一件事情一定也适用于派生类,因为每一个派生类对象也都是一个基类对象。

条款 33: 避免遮掩继承而来的名称

  • 派生类内的名称会遮掩基类内的名称。在 public 继承下从来没有人希望如此。
  • 为了让被遮掩的名称再见天日,可使用 using 声明式或转交函数(forwarding functions)。

条款 34: 区分接口继承和实现继承

  • 接口继承和实现继承不同。在 public 继承之下,派生类总是继承基类的接口。
  • 纯虚函数只具体指定接口继承。
  • 非纯虚函数(impure virtual)具体指定接口继承及缺省实现继承。
  • 实函数具体指定接口继承及强制性实现继承。

条款 35: 考虑虚函数以外的其他选择

  • todo

条款 36: 绝不重新定义继承而来的实函数

条款 37: 绝不重新定义继承而来的缺省参数值

  • 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定的,而虚函数确实动态绑定的。

条款 38: 通过复合塑膜出 has-a 或“根据某物实现出”

条款 39: 明智而审慎地使用 private 继承

  • private 继承以为着“根据某物实现出”。它通常比复合的界别低。但当派生类需要访问基类的 protected 成员,或者需要重新定义继承而来的虚函数时,这么设计是合理的。
  • 和复合不同,private 继承可以造成 empty base 最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

条款 40: 明智而审慎地使用多重继承

  • 多重继承比单一继承复杂。它可能导致新的歧义性,以及对 virtual 继承的需要。
  • virtual 继承会增加大小、速度、初始化及赋值复杂度等等成本。如果虚基类不带有任何数据,僵尸最具实用价值的情况。
  • 多重继承的确有正当用途。如 public 继承某个接口类和 private 继承某个协助实现的类的两相结合。

条款 41: 了解隐式接口和编译器多态

条款 42: 了解 typename 的双重意义

  • 声明 typename 参数时,前缀关键字 classtypename 可互换。
  • 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class list(基类列)或 member initialization list(成员初始列)内以它作为 base class 修饰符。

条款 43: 学习处理模板化基类内的名称

  • 可在 derived class templates 内通过 this-> 指涉 base class templates 内的成员名称,或一个明白写出的 base class 资格修饰符完成。

条款 44: 将与参数无关的代码抽离 templates

  • templates 生成多个 classes 和多个函数,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系。
  • 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或 class 成员变量替换 template 参数。
  • 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×