构造函数与拷贝控制
和其他类一样,继承体系中的类也需要控制自己的拷贝控制操作,包括:拷贝构造、拷贝赋值运算符、移动、析构。如果一个类(不管是基类还是派生类)没有定义拷贝控制操作,编译器会为它合成一个版本。
1.虚析构函数
①基类通常应该定义一个虚析构函数,这样就能动态分配继承体系中的对象了
当我们delete一个动态分配的对象的指针时将执行析构函数,但如果该指针指向继承体系中的某个类型,就可能出现指针的静态类型和被删除对象的动态类型不符合的情况。例如:
Quote *itemp = new Quote; //静态类型与动态类型一致
delete itemp; //调用Quote的析构函数
itemp = new BulkQuote; //静态类型与动态类型不一致
delete itemp; //调用BulkQuote的析构函数
如果静态类型指针itemp的动态类型是BulkQuote的,那么它就应该执行BulkQuote的析构函数。我们通过在基类中将析构函数定义成虚函数以保证执行正确的析构函数版本:如果我们delete一个指向派生类对象的基类指针,则需要在基类中定义虚析构函数。
如果基类的析构函数不是虚函数,那么delete一个指向派生类对象的基类指针将产生未定义的行为
②基类的析构函数不适用于三五法则
三五法则:一个类如果需要析构函数,那么它一定需要拷贝构造和拷贝赋值运算符。
但基类的析构函数不适用于此法则。
③虚析构函数将阻止合成移动操作
如果一个类定义了析构函数,那么编译器不会为这个类合成移动操作。
2.合成拷贝控制与继承
①基类或派生类合成的构造、赋值或析构函数
基类或派生类合成的拷贝控制成员行为与其他合成的类似:它们对本身的成员依次进行初始化、赋值或销毁。此外,这些合成的成员还负责使用直接基类中对应的操作对一个对象的直接基类进行初始化、赋值或销毁。
BulkQuote的默认构造函数构造过程:
- 运行DiscQuote的默认构造函数,DiscQuote的默认构造函数又运行Quote的默认构造函数。
- DiscQuote的默认构造函数完成后,继续执行BulkQuote的构造函数依次对其本身的成员进行初始化。
Quote的构造体系中,基类Quote显式的使用合成的虚析构函数,而其派生类都使用默认的析构函数。
②移动操作与继承
因为基类定义一个虚析构函数,基类通常不含合成的移动操作,而且在它的派生类中也没有合成的移动操作。
因此当我们需要移动操作时,应该先在基类中进行定义,它的派生类如果没有定义析构函数将会合成移动操作。
3.派生类的拷贝控制成员
派生类的构造函数在初始化阶段不但要初始化派生类自己的成员,还要负责初始化派生类对象的基类部分。因此,派生类的拷贝构造和移动构造函数在拷贝和移动自有成员的同时,也要拷贝和移动基类部分的成员。类似的,派生类赋值运算符也必须为其基类部分的成员赋值。
和构造函数及赋值运算符不同,析构函数只负责销毁派生类自己分配的资源,派生类对象的基类部分也是自动销毁的。
①派生类赋值运算符
派生类的赋值运算符必须显示地为其基类部分赋值:
class Base {...};
class D : public Base {
public:
//其他成员
D& operator=(const D &rhs) {
this->Base::operator=(rhs); //为基类部分赋值
//为派生类自己地成员赋值
return *this;
}
};
Base::operator=的调用语句将执行Base的拷贝赋值运算符。
②派生类析构函数
在析构函数体执行完成后,对象的成员会被隐式销毁。类似地,对象的基类部分也是隐式销毁地,因此,和构造函数和赋值运算符不同的是,派生类析构函数只需要负责销毁派生类自己分配的资源。
class D : public Base {
public:
//Base::~Base()自动调用执行
~D() {
//处理派生类自己分配的资源
}
}