15.7 构造函数和拷贝控制
15.7.1 虚析构函数
在继承体系中,基类的指针,引用可以绑定派生类的对象,使用指针时,我们使用delete来回收这个指针所管理的对象。但是在继承体系中,这个指针可能指向的是其子类的对象,为了正确的调用子类的析构函数,应该将基类的析构函数声明为虚析构函数。
只要基类的析构函数为虚析构函数,那么其子类的析构函数(编译器合成的或者自己定义的)也默认是析构函数。
注意,一旦我们显式的写出了析构函数,那么编译器将不会为我们合成移动操作。
练习
15.24
在继承体系中的类需要虚析构函数,和普通析构函数一样虚析构函数也需要回收自己类中指针所管理的资源。
15.7.2 合成拷贝控制与继承
派生类合成的拷贝构造、拷贝赋值、析构函数在执行时会调用其父类对应的操作。
对于拷贝构造和默认构造都是先调用基类的构造函数(在派生类的初始化列表中),再调用派生类的构造函数。
而析构函数则是先调用派生类的析构,再调用基类的析构。
如果基类中某一个拷贝控制操作(拷贝构造,默默认构造,拷贝赋值,移动构造,移动赋值,析构)被定义为删除或者不可访问,则派生类对应的操作也被定义为删除。
特别的,如果基类的析构函数被定义为删除,则派生类的合成的默认构造函数和拷贝构造函数将被定义为删除。(这一点感觉只能硬记)
练习
15.25
如果不定义Disc_quote的默认构造函数,因为其定义了其他的构造函数,所以默认构造函数就变成了以删除的函数,那么Bulk_quote的默认构造函数因为无法调用其父类的构造函数也将变为删除的函数。
15.7.3 派生类的拷贝控制成员
派生类默认的拷贝控制成员会调用基类拷贝控制成员的对应版本。
构造函数
如果我们显式的定义了拷贝构造函数,则也需要显式调用基类的拷贝构造函数,否则其将调用基类的默认构造函数来初始化基类的成员。
class A
{
public:
A(const A& a) {
};
virtual void func() {
cout<<"A::func"<<endl;
}
A(A&& a) {
}
private:
};
class B:public A
{
public:
B(const B& b):A(b) {
};
B(B&& b):A(std::move(b)) {
}
private:
};
在B的拷贝构造函数中,调用A的拷贝构造函数,这可以传入b的对象。
B(const B& b):A(b) {
};
在B的移动构造函数中,调用A的移动构造函数,这里就有问题了,为什么移动构造函数中需要b本来就是右值,还要调用move来传入A的移动构造函数呢。
这里其实是一个误区,因为b绑定的对象是右值,但是b对象本身其实是一个左值。所以还需要将b传入move这也才能正确的调用A的移动构造函数。
B(B&& b):A(std::move(b)) {
}
赋值运算符
不同于拷贝构造,赋值运算符只能在函数体中调用父类的辅助运算符。调用方式就是operator=()
B& operator=(const B&b) {
A::operator=(b