15-7 派生类拷贝控制

15.7.1 虚析构函数

在基类中将析构函数定义为虚函数,用动态绑定机制来保证执行正确的析构函数版本

class Base{
public:
   	virtual ~Quote() = default; //动态绑定析构函数
};

//Derived是Base的派生类
Base *itemP = new Base;		//静态类型与动态类型一致
delete itemP;				//调用Base的析构函数
itemP = new Derived;		//静态类型与动态类型不一致
delete itemP;				//调用Derived的析构函数

一个基类总是需要一个析构函数,且必须为虚函数

15.7.2 合成拷贝控制与继承

本节不探讨移动操作,只探讨合成的默认构造函数以及合成的默认析构函数

派生类合成的默认构造函数

  1. 依次初始化派生类本身的成员
  2. 调用直接基类的合成的默认构造函数

如:Quote是基类,Disc_quote是Quote的派生类,Bulk_quote是Disc_quote的派生类

  • 合成的Bulk_quote默认构造函数运行Disc_quote 的默认构造函数,后者又运行Quote 的默认构造函数。

  • Quote 的默认构造函数将bookNo成员默认初始化为空字符串,同时使用类内初始值将price初始化为0。

  • ouote的构造函数完成后,继续执行Disc_quote 的构造函数,它使用类内初始值初

    始化 qty和 discount。

    Disc_quote 的构造函数完成后,继续执行Bulk_quote的构造函数,初始化Bulk_quote类的成员

如果基类构造了接受参数的构造函数而没有定义默认构造函数,派生类无法合成默认的构造函数

class Base{
public :
    //Base没有默认构造函数
    Base(int i);
};

class D : public Base{
public :
    //派生类D无法合成默认的构造函数
};

无法创建D对象,因为它无法初始化

D d; //报错:无法引用“D”的默认构造函数--它是已删除的函数

派生类合成的默认析构函数

  1. 销毁派生类自己的成员
  2. 销毁派生类的直接基类

同样:如果基类中的析构函数无法访问或被定义为删除的,那么派生类中的析构函数也是删除的,因为派生类无法析构它的基类部分

同时,派生类的合成的默认构造函数和拷贝函数也是删除的,因为派生类无法析构它的基类部分

class Base{
public :
private:
    //基类的合成的默认析构函数是删除的
    ~Base();
};

class D : public Base{
public :
    //派生类D的合成的默认构造函数、析构函数都是删除的
};

总结

派生类的默认构造函数、拷贝构造函数、拷贝赋值运算符、析构函数都会调用基类的对应函数

如果基类的对应函数是无法访问或者删除的,那么在派生类中也是删除的

15.7.3 派生类的拷贝控制成员

直接用代码来说明问题

派生类的拷贝构造函数

class Base {/*...*/};
class D: public Base {
public : 
    //默认情况下,基类的默认构造函数初始化对象的基类部分
    //想要使用拷贝或移动构造函数,我们必须在构造函数的初始值列表中
    //显式地调用该构造函数
    D(const D& d): Base(d)	//拷贝基类成员
        /* D的成员的初始值 */{/**/}
    
    D(D&& d): Base(std::move(d))  //移动基类成员
        /* D的成员的初始值 */{/**/}
};

如果没有提供基类的初始值

//D的这个拷贝构造函数很可能是不正确的
//基类部分被默认初始化,而非拷贝
D(const D& d): /* 成员初始值,没有提供基类初始值*/
	{/* ...*/}

派生类赋值运算符

显示为基类部分赋值

//Base::operator=(const Base&)不会被自动执行
D& D::operator=(const D &rhs){
    Base::operator=(rhs); //为基类部分赋值
    //按照过去的方式为派生类成员赋值
    //酌情处理自赋值及释放已有资源等情况
    return *this;
}

派生类的析构函数

与拷贝构造函数和赋值运算符不同,派生类的析构函数只负责析构自身的成员

class D:public Base{
public :
    //Base::~Base被自动调用执行
    ~D() {/* 该处由用户定义清除派生类成员的操作 */}
};

对象销毁的顺序正好与其创建的顺序相反:派生类析构函数首先执行,然后是基类的析构函数,以此类推,沿着继承体系的反方向直至最后。

15.7.4 继承的默认构造函数

派生类“继承”基类构造函数的方式是提供一条注明了(直接)基类名的using声明语句。

举个例子,我们可以重新定义Bulk_quote类(参见15.4节,第541页),令其继承Disc_quote类的构造函数:

image-20220303184747904

通常情况下,using声明语句只是令某个名字在当前作用域内可见。而当作用于构造函数时,using声明语句将令编译器产生代码

对于基类的每个构造函数,编译器都生成一个与之对应的派生类构造函数。换句话说,对于基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。

在我们的 Bulk_quote类中,继承的构造函数等价于:

image-20220303184914182

注意点

  1. 基类构造函数的默认实参不会被继承

被继承。相反,派生类将获得多个继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参。

例如,如果基类有一个接受两个形参的构造函数,其中第二个形参含有默认实参,则派生类将获得两个构造函数:一个构造函数接受两个形参(没有默认实参),另一个构造函数只接受一个形参,它对应于基类中最左侧的没有默认值的那个形参。

  1. 如果基类含有几个构造函数,则除了两个例外情况,大多数时候派生类会继承所有这些构造函数。

第一个例外是派生类可以继承一部分构造函数,而为其他构造函数定义自己的版本。如果派生类定义的构造函数与基类的构造函数具有相同的参数列表,则该构造函数将不会被继承。定义在派生类中的构造函数将替换继承而来的构造函数。

第二个例外是默认、拷贝和移动构造函数不会被继承。这些构造函数按照正常规则被合成。继承的构造函数不会被作为用户定义的构造函数来使用,因此,如果一个类只含有继承的构造函数,则它也将拥有一个合成的默认构造函数。

posted @ 2022-03-04 12:48  咪啪魔女  阅读(81)  评论(1编辑  收藏  举报