深入理解C++面向对象机制(三)构造与析构

深入理解C++面向对象机制(三)构造与析构


零.声明


1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能够帮助那些想深入了解C++的网友。

2.文章中会有一些被称为“编译器生成的代码”,这些代码并不是编译器真正的生成代码,只是为了方便讨论而写的模拟代码。

3.如果觉得文章对你有帮助而需要转载,也请阁下能够注明出处。

4.如果觉得博文对问题的讨论有误,也可以给博主留言。

 

一.引入


我们都知道你实例一个类对象时,系统回去调用构造函数。当我们销毁一个类对象的时候,系统回去调用改类的析构函数。

所以我们可以把一部分初始化工作放在构造函数中,同样道理把一些类的收尾工作放在析构函数中。

这一篇文章,会对这两个函数做较为详细的讨论。

 

二.构造函数


1. 编译器下的构造函数


编译器为了实现多态等特性,构造函数中被安插了许多额外的代码。接下来按照顺序来看一下一个构造函数中会出现的情况。(如果觉得下面的叙述太过枯燥。可以先直接跳过看例子,然后在回头看下面这段说明。)

 

1.最被执行的是虚继承下的基类的构造函数。所有virtual base class constructors必须被调用,从左到右,从最深到最浅。

如果当前类是class obect是最底层(most-derived,这一个参数在虚继承中有讨论过)的class,那么基类的构造函数constructors被调用。

存放virtual base subobject的偏移量必须在执行期的时候被存放。

2.解决了虚继承,接下来就是多继承或者普通继承。所有上一层的base class constructors必须被调用,以base class的声明顺序。

如果base class是剁成继承下的第二或后继base class,那么this指针必须调整。

3.基类的事情完成之后,那么就是指向virtual table的指针必须被附上正确的地址。

4.类构造函数的memberinitialization list中的成员会被放在构造函数体内,以这些成员变量的声明顺序执行初始化操作。

5.如果当前类有成员,并且没有在第4条的member initialization list中,并且这个成员有默认的构造函数,那么这个成员的类构造函数必须被调用。

 

2.例子


上一小节,我们描述了一个“完整”的构造函数。比较抽象,所以下面提供一个实例,来说明一个完整地构造函数。

class CX;
 
class CBase1;
 
class CBase2;
 
class CBase3 : public virtual CBase2;
 
class CBase4 : public virtual CBase2;
 
class CDerived : public CBase1, CBase3, CBase4
{
public:
    CDerived();
    CDerived(int i) : m_id(i);
    ~CDerived();
private:
    int m_id;
    CX m_x;
};
 
CDerived::CDerived(int i) : m_id(i)
{
 
}
 
CDerived * CDerived::CDerived(CDerived * this,bool _b_most_derived,int i) : m_id(i)
{
    if (_b_most_derived)
        this->CBase2::CBase2();
    _b_most_derived = false;
    _n_offset_cbase2 = sizeof(CDerived)-sizeof(CBase2);
 
    this->CBase1::CBase1(this);
    this->CBase3::CBase3(this + offset_cbase3,_b_most_derived);
    this->CBase4::CBase4(this + offset_cbase4,_b_most_derived);
 
    this->vptr_CBase2 =vbtl_CDerived_CBase2;
    this->vptr_CBase1 =vbtl_CDerived_CBase1;
    this->vptr_CBase3 =vbtl_CDerived_CBase3;
    this->vptr_CBase4 =vbtl_CDerived_CBase4;
    //
    m_id = i;
    m_x.CX::CX();
 
    return this;
 
}

上面代码省略了基类的具体定义。

CBase3和CBase4都虚继承于CBase2;CDerived多继承于CBase1、CBase3和CBase4。

我们挑选了CDerived有memberinitialzation list的构造函数。

1.构造函数最开始便判断CDerived是不是mostderived的,如果是我们就调用虚基类CBase2的构造函数。并计算得到虚基类的subobject在CDerived对象中的偏移量。

2.然后开始按照声明顺序调用CBase1::CBase1(),CBase3::CBase3()和CBase4::CBase4()。

3.之后便对virtual table指针赋值。

4.再之后,便将member initalzation list搬入函数体内。

5.最后调用成员m_x的类默认构造函数(我们假设CX有默认的构造函数)。

这就是构造函数。

这里有几点需要说明,虚继承下的类对象分布按照《深入理解C++面向对象机制(二)-虚继承》中讨论的模型,virtual base subobject放在类对象的最底部。

然后其中几个类subobject的偏移量的计算,我并没有去确认过,所以用了几个变量(offset_cbase3和offset_cbase4)代替。

 

三.析构函数


析构函数相对于构造函数要简单得多。

1.  首先,如果类成员有定义了析构函数,那么按照声明的反顺序依次调用。

2.  重新设定virtual table指针的值,以指向正确的地址。

3.  如果非虚继承基类定义了析构函数,那么按照声明的反顺序依次调用。

4.  如果有任何virtual base clase定义了析构函数,而且当前类是most derived的,那么按照原先构造顺序的反顺序调用。

我们可以发现,析构函数的调用顺序和构造函数式相反的。

 

四.结尾


这个系列也在这篇结尾,当然如果后面了解了值得分享的东西,仍会继续添加进来。

这里再次感谢能有《深度探索C++对象模型》这样的书籍,能这么深入介绍C++。还有几篇文章中肯定存在不少错误,也请读者们见谅,如果可以的话发现了也愿好心指出。

 

 

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页