15.6 构造函数与拷贝控制
在基类中必须将析构函数定义为虚函数,这样动态绑定时会调用动态类型自己的析构函数版本
合成拷贝控制与继承
继承关系
Bulk_quote -> Disc_quote -> Quote
- 合成的
Bulk_quote
默认构造函数运行Disc_quote
的默认构造函数,后者又调用Quote
的默认构造函数 Quote
的构造函数完成后,继续执行Disc_quote
的构造函数Disc_quote
的构造函数完成后,再执行Bulk_quote
的构造函数
派生类中删除的拷贝控制与基类的关系
- 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的函数或不可访问的,则派生类中对应的成员也是被删除的,原因是编译器不能使用基类成员类执行派生类对象中基类部分的构造、赋值或销毁操作
class B {
public:
B();
B(const B&) = delete;
// 没有移动构造函数
};
class D: public B {
// 没有声明任何构造函数
};
D d; // 正确,D的合成默认构造函数调用B的默认构造函数
D d2(d); // 错误,D的合成拷贝构造函数也是delete,也没有移动构造函数
D d3(std::move(d)); // 错误,D中没有移动构造函数,所以会调用拷贝构造函数,但是拷贝构造函数是delete,所以调用失败
B定义了拷贝构造函数,所以编译器不会生成移动构造函数,所以不能拷贝或移动B的对象
如果定义了一个移动构造函数或移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符将被定义为delete
默认情况下,基类的默认构造函数初始化对象的基类部分
派生类要想使用拷贝或移动构造函数,必须在构造函数初始化列表中显式调用基类的构造函数
class Base {};
class D: public B {
D(const D& d): Base(d) {}
D(D&& d): Base(std::move(d)) {}
};
派生类的赋值运算符也必须显式调用基类的赋值运算符
D& D::operator=(const D& rhs) {
Base::operator=(rhs); // 为基类部分赋值
...
};
派生类的析构函数会自动调用基类的析构函数
class D: public Base {
public:
// Base::~Base()被自动调用
~D() {}
};
构造函数或析构函数中如果调用了虚函数,应该执行与构造函数或析构函数所属类型相对应的虚函数版本
当基类的构造函数调用一个虚函数时,如果调用了派生类的虚函数版本,它可能会访问派生类的成员,而此时还在构造基类,派生类部分还没构造完成,就会出错
派生类可以使用using
声明语句,直接使用基类的构造函数
class Bulk_quote: public Disc_qute {
public:
using Disc_quote::Disc_quote(); // 继承Disc_quote的构造函数
};
// 等价于
Bulk_quote(const string& book, double price, size_t qty, double disc):
Disc_quote(book, price, qty, disc) {}