深入理解C++面向对象机制(二)虚继承

深入理解C++面向对象机制(二)虚继承


零.声明


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

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

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

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

 

一.引入


我们先来看一个例子。

class CX ; class CBase1 ; class CBase2 ; class CDerived;

其中CBase1和CBase2都是CX的派生类,CBase1和CBase2都是CDerived的基类。当我们有一个CDerived的对象的时候,我们只想要一个CX的subobject。在上一篇文章讨论的多继承下,CDerived对象会存在CBase1的subobject和CBase2的subobject。而这两个subobject都会包含CX的subobject。

这样一个CDerived对象就会有两个CX的subobject,这样和我们的要求不一样(我们只想要一个呀!!!)

所以就有了虚继承。

 

二.虚继承的实现


1. 例子


按照讨论的惯例,我们先提供一个虚继承的例子。

class CX
{
public:
    CX();
    virtual ~CX();
private:
    int m_x;
public:
    virtual void Fun();
};
 
class CBase1 : public virtual CX
{
public:
    CBase1();
    virtual ~CBase1();
private:
    int m_y;
public:
    virtual void Fun();
    virtual void Fun1();
};
 
class CBase2 : public virtual CX
{
public:
    CBase2();
    virtual ~CBase2();
private:
    int m_z;
public:
    virtual void Fun();
    virtual void Fun2();
};
 
class CDerived : public CBase1,CBase2
{
public:
    CDerived();
    ~CDerived();
private:
    int m_f;
public:
    void Fun();
    void Fun1();
    void Fun2();
    virtual void Fun3();
};


接下来便是virtual table图。

 

图1.0

 

这张virtual table图相对于之前,变得更加复杂。对于这张图有一些要说明的(以免引起误会)。

首先这次我将vptr放在类的底部,之前这个指针都是放在类的顶部的。放在底部仅为了讨论的方便。并没有规定编译器一定要将vptr放在类的顶部或者底部。

其次虚继承下的,virtual table又多了一个“offset to CX subobject”,这个槽的序号往往是一个负数,比如这里便是-1;type_info的槽序号便是0;虚函数槽都是正数。

而offset这是为了将this指针移向virtual base subobject的一个偏移量。

根据上一篇文章多继承的讨论,我们将CDerived第一个基类CBase1的virtual table作为主表,第二个基类CBase2的virtual table作为次表。

这里的变化就是又多了一个CX的virtualtable。

最后我们在CDerived的class图中,就会发现只有一个CX的subobject,而我们只在CBase1和CBase2的virtual table中存放CX subobject的位置(通常就是一个offset)。

 

2.虚继承下的构造函数


上一小节,我们展现了虚继承下的virtual table图和它是如何实现虚函数的。那么我们怎么样使CX只构造一次呢?

我的意思是如何抑制CX调用两次构造函数。

最常用的做法,在构造函数中再安插一个bool的参数,_b_most_derived。

现在我们展现一下CDerived的构造函数在编译器下展开成什么样。

CDerived::CDerived(CDerived * this,bool _b_most_derived)
{
    //
    if (!_b_most_derived)
        this->CX::CX();
    _b_most_derived = false;
    this->CBase1::CBase1(_b_most_derived);
    this->CBase2::CBase2(_b_most_derived);
    //省略
}


上面的代码就是对于这个问题的解决方法。编译器使用_b_most_derived去抑制上层再去调用CX的构造函数。

这里需要说明的是,构造函数的展开远没有上面代码这么简单。这里只为讨论本文的主题,故而省去了许多细节。对于构造函数的详细讨论将在下一篇博文中讨论。

 

三.结尾


这里我再次声明一次,上面的virtual table不是编译器真的会这么生成。博主只是为方便讨论才做的,当然也参考了《深度探索C++对象模型》。这里只是说明博主所介绍的只是一种编译器实现虚继承的一种方法。

各种继承方式的多态特性也讨论完毕。下面的文章将介绍构造函数和析构函数。这两个函数看似简单,却暗藏了太多东西。只有深入了解了C++对象模型之后,才能真正去了解构造函数和析构函数的意义。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章