1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能够帮助那些想深入了解C++的网友。
2.文章中会有一些被称为“编译器生成的代码”,这些代码并不是编译器真正的生成代码,只是为了方便讨论而写的模拟代码。
3.如果觉得文章对你有帮助而需要转载,也请阁下能够注明出处。
4.如果觉得博文对问题的讨论有误,也可以给博主留言。
我们都知道你实例一个类对象时,系统回去调用构造函数。当我们销毁一个类对象的时候,系统回去调用改类的析构函数。
所以我们可以把一部分初始化工作放在构造函数中,同样道理把一些类的收尾工作放在析构函数中。
这一篇文章,会对这两个函数做较为详细的讨论。
编译器为了实现多态等特性,构造函数中被安插了许多额外的代码。接下来按照顺序来看一下一个构造函数中会出现的情况。(如果觉得下面的叙述太过枯燥。可以先直接跳过看例子,然后在回头看下面这段说明。)
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中,并且这个成员有默认的构造函数,那么这个成员的类构造函数必须被调用。
上一小节,我们描述了一个“完整”的构造函数。比较抽象,所以下面提供一个实例,来说明一个完整地构造函数。
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++。还有几篇文章中肯定存在不少错误,也请读者们见谅,如果可以的话发现了也愿好心指出。