copy from(https://blog.csdn.net/billcyj/article/details/78888074)
15.7 构造函数和拷贝控制
创建、拷贝、移动、赋值和销毁
15.7.1 虚析构函数
继承体系中的析构函数应该定义为虚函数。
以前说过,定义了析构函数就要定义拷贝和赋值,但这里基类的析构函数是例外。
为什么?
保证可以动态分配继承体系中的对象。且delete时能执行对应的析构函数。
若指针指向继承体系中的某个类型,则可能出现指针的静态类型和被删除对象的动态类型不符合:
如:delete一个Quote*类型指针,该指针指向Bulk_quote类型对象。那应该执行Bulk_quote的析构函数。
virtual ~Quote()=default;
15.7.2 合成拷贝控制与继承
- 派生类中定义为删除的拷贝控制与基类的关系
class B
{
public:
B(); //默认构造函数声明
B(const B&) = delete; //定义为删除的拷贝构造函数
//既然定义了拷贝构造函数,就不会合成移动操作了
};
class D : public B
{
//没有声明任何东西,只是单纯继承了B
};
D d; //正确:D的合成默认构造函数调用B的默认构造函数,自己反正也没成员
D d2(d); //错误:因为B的拷贝构造函数是delete的,所以D的也是delete,无法被调用
- 移动操作与继承
移动操作:基类多有虚析构函数,所以不会合成移动操作,如果确实需要,应首先在基类中显式定义,此时要同时显式定义拷贝操作:
class Quote
{
public:
Quote() = default; //强行合成默认构造函数
Quote(const Quote&) = default; //拷贝构造函数
Quote(Quote&&) = default; //移动构造函数
Quote& operator=(const Quote&) = default; //拷贝赋值
Quote& operator=(Quote&&) = default; //移动赋值
virtual ~Quote() = default; //虚析构函数
};
如此,Quote的派生类也会自动获得合成的移动操作。
15.7.3 派生类的拷贝控制成员
派生类的拷贝和移动构造函数在拷贝和移动自有成员的同时,也要拷贝和移动基类部分,赋值运算符也类似。
而析构函数只负责销毁派生类自己分配的资源 基类部分自动销毁
- 定义派生类的拷贝或移动构造函数
class Base
{
};
class D : public Base
{
//我们要拷贝或移动基类部分,就必须在派生类的构造函数初始值列表中显式调用
public:
D(const D& d) : Base(d) {} //调用基类的拷贝构造函数来拷贝基类部分
//这儿比较特殊的是Base(d)会去匹配Base的拷贝构造函数,虽然人家其实接受B类型
D(D&& d) : Base(std::move(d)){}
};
- 派生类赋值运算符
也就是拷贝赋值和移动赋值。
D &D::operator=(const D &rhs)
{
Base::operator=(rhs); //显式地为基类部分赋值:(合成的或自定义的)基类的拷贝赋值运算符将释
//放掉左侧对象的基类部分的旧值,然后利用rhs为其赋一个新值
//接下来为派生类自己的部分赋值(省略)
return *this;
}
- 派生类析构函数
对象销毁的顺序与创建顺序相反:派生类析构函数先执行,然后执行基类的析构函数。
也就是,对直接基类部分拷贝后,再拷贝类本身的成员;销毁本身后再销毁基类,基类部分会自动销毁。
禁止在构造或析构函数中调用虚函数
假设我们在基类的构造函数中调用派生类的某个函数去访问其成员,这会派生类对象还没构建完成,怎么能访问呢?于是,C++禁止了这种行为。
15.7.4 继承的构造函数
1.派生类类不能继承基类的默认构造函数、拷贝构造函数、移动构造函数;
2.但派生类能够重用其基类自定义的构造函数;
3.派生类中的基类成员调用基类的构造函数初始化,派生类中的其他成员将被默认初始化
class Bulk_quote : public Disc_quote
{
public:
using Disc_quote::Disc_quote; //使用using继承Disc_quote的构造函数
};
把using放到构造函数上时,using声明语句将令编译器产生代码,而且,编译器会生成一个与基类对应的派生类构造函数,针对上面的例子生成的派生类构造函数如下
Bulk_quote(const string& book, double price, size_t qty, double disc) : Disc_quote(book, price, qty, disc) {}
- 继承的构造函数的特点
1.构造函数的using声明不会改变该构造函数的访问级别
2.如果基类的构造函数是explicit或constexpr的,则继承的构造函数也是
3.当一个基类构造函数含有默认实参,派生类将获得多个继承的构造函数,其中每个构造函数分别忽略掉一个含有默认实参的形参
另外,如果派生类自己又定义了一个和基类构造函数具有相同参数列表的构造函数,那么基类的这个构造函数不会被继承,就用派生类自己定义的就好了,覆盖了
15.8 容器与继承
我们不能把具有继承关系的多种类型的对象直接存放在容器中:
vector<Quote> basket;
basket.push_back(Bulk_quote("a", 50, 10, 0.25));
basket的元素是Quote对象,因此当我们向其中添加Bulk_quote对象时,属于派生类的部分会被忽略
容器中应放置指针(最好是智能指针)而非对象
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("a", 50));
basket.push_back(make_shared<Quote>("b", 50, 10, 0.25));
cout << basket.back()->net_price(15) << endl; //打印折扣后的价格,这回派生类对象是完整的
基类的指针 则指向对象的动态类型可能是基类也可能是派生类
派生类应反映与基类的Is A关系,公有派生类的对象应该可以用在任何需要基类对象的地方;类之间Has A则是包含成员的意思。
对于C++面向对象的编程来说,并不是直接用对象,用的是指针和引用。