10.3利用父类
编写派生类时,需要知道父类和派生类之间的交互方式。创建顺序、构造函数链和类型转换都是潜在的 bug来源。
1.父类构造函数
对象并不是突然建立起来的,创建对象时必须同时创建父类和包含于其中的对象。C++定义了如下的创建顺序:
- (1)如果某个类具有基类,则执行基类的默认构造函数。除非在 ctor-initializer 中调用了基类构造函数,此时调用这个构造函数而不是默认构造函数。
- (2)类的非静态数据成员按照声明的顺序创建。
- (3)执行该类的构造函数。
可递归使用这些规则。如果类有祖父类,祖父类就在父类之前初始化.
C++将自动调用父类的默认构造函数(如果存在的话)。如果父类的默认构造函数不存在,或者存在默认构造函数但希望使用其他构造函数,可在构造函数初始化器(constructor initializer)中像初始化数据成员那样链接(chain)构造函.
class Something
{
public:
Something() {cout << "2";}
};
class Base
{
public:
Base() {cout << "1";}
};
class Derived : public Base
{
public:
Derived() {cout << "3";}
private:
Something m_dataMember;
};
int main()
{
Derived myDerived;
}
虚方法的行为在构造函数中是不同的、如果派生类重写了基类中的虚方法、从基类构造函数中调用虚方法,就会调用虚方法的基类实现而不是派生类中的重写版本。
2.父类的析构函数
由于析构函数没有参数,因此始终可自动调用父类的析构函数。析构函数的调用顺序刚好与构造函数相反∶
- (1)调用类的析构函数。
- (2)销毁类的数据成员,销毁顺序与创建的顺序相反。
- (3)如果有父类,调用父类的析构函数。
也可递归使用这些规则。链的最底层成员总是第一个被销毁。
3.使用父类方法
在派生类中重写方法时,将有效地替换原始方法。然而,方法的父类版本仍然存在,仍然可以使用这些方法。
4.向上转型和向下转型
如果用派生类对基类的指针或引用赋值,则不会产生截断:
Base& myBase {myDerived};// No slicing
这是通过基类使用派生类的正确途径,也称为向上转型(upcasting)。这也是让方法和函数使用类的引用而不是直接使用类对象的原因。通过使用引用,派生类在传递时没有发生截断。
向下转型有时是必需的,在可控环境中可充分利用这种转换。然而,如果打算进行向下转型,应该使用 dynamic_cast()
,以使用对象内建的类型信息,拒绝没有意义的类型转换。这种内建信息通常驻留在虚表中,这意味着dynamic_cast()
只能用于具有虚表的对象,即至少有一个虚编号的对象。如果针对某个指针的dynamic_cast()
失败,这个指针的值就是 nullptr
,而不是指向某个无意义的数据。如果针对对象引用的dynamic_cast()
失败,将抛出 std::bad_cast
异常。本章的最后一节将会详细讨论类型转换。