第二章: 构造/析构/赋值运算
条款5: 了解 C++ 默默编写并调用哪些函数
- 当 C++ 进行处理之后,一个空类就不再是空类了。因为编译器会为它声明编译器版本的一个 默认构造函数,拷贝构造函数,拷贝赋值运算符 和 析构函数。这些函数都是 public 的且 inline 的。
- 编译器版本的 默认构造函数和析构函数主要是 调用基类和非静态成员变量 的构造函数和析构函数。编译器产出的 析构函数 是一个 非虚函数,除非这个类的基类自身声明了 虚析构函数。
- 编译器版本的 拷贝构造函数 和 拷贝赋值运算符 只是单纯地将来源对象的每一个非静态成员变量拷贝到目标对象。
- 如果一个 类中包含 引用和常量成员,而 C++ 并不允许 让引用或常量 改指向不同对象。面对这个情况,C++的响应是拒绝编译那一行赋值动作。因此我们必须自己定义 拷贝赋值运算符 以处理 支持 内含引用成员以及常量成员 的类 的赋值操作。
- 如果某个 基类 将拷贝赋值运算符 声明为 私有的,那编译器也将拒绝为其派生类 生成一个 拷贝赋值运算符。
条款6: 若不想使用编译器自动生成的函数,就应该明确拒绝
- 如果我们想要 阻止拷贝操作,我们可以将 拷贝构造函数 或 拷贝赋值运算符 声明为 private,这阻止了编译器暗自创建其专属版本。但这个做法并不绝对安全,因为 成员函数以及友元函数 都还是可以调用到这些私有函数。
- 我们也可以将成员函数声明为私有同时故意不实现它们,这被用在 C++ iostream库中 阻止拷贝行为。
- 将 拷贝构造函数 以及 拷贝赋值运算符 在一个专门为了 阻止拷贝动作 而设计的基类内 声明为 private:
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeforSale : private Uncopyable{
...
}
- 由于它总是扮演 基类,因此使用这项技术有可能导致多重继承。
条款7: 为多态基类声明虚析构函数
- 依靠用户来执行 delete操作,基本上就带有某些错误倾向。
- 基类有一个非虚析构函数,当派生类对象经由一个基类指针被删除,该基类带有非虚析构函数,那其结果就是 实际执行时通常发生的是对象的派生成分没被销毁。这会导致资源泄露,浪费调试时间。
- 任何 类,只要带有 虚函数 那几乎确定应该也有一个 虚析构函数。
- 当 类 不企图被当做 基类,令其析构函数成为虚函数往往是个馊主意。(虚函数指针需要额外的内存空间)
- 虚函数指针 被用来在运行期决定哪一个 虚函数应该被调用,其指向一个由函数指针构成的数组,被称为虚函数表。
- 包含虚函数的类,其对象体积会增加,同时该对象也不再和其他语言 (如 C) 内的相同声明有着一样的结构 (因为其他语言的对应物中不包含虚函数指针)。因此,无端地将所有类的析构函数声明为 virtual,就像从未声明它们为virtual一样,都是错误的。
- string,vector,list,set,unordered_map 等 类不含 虚析构函数,我们不能错误地把它们当成基类。
- 当我们希望拥有抽象类,但手头上没有任何纯虚函数,那解决方法可以是: 为我们希望成为 抽象的类 声明一个 纯虚析构函数。
- 析构函数的运作方式: 最深层派生的那个类的析构函数最先被调用,然后是其每一个基类的析构函数被调用。
- 并非所有基类的设计目的都是为了多态用途 (如 string 和 STL容器)。某些类的设计目的是作为基类而不是多态,如之前的 Uncopyable,它们并非被设计用来 经由基类接口 处置 派生类对象。