深入理解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++對象模型之後,才能真正去了解構造函數和析構函數的意義。

 

 

 

 

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