C++虛基類構造函數詳解(調用順序)之一


  • 解釋某個函數,我通常的講解不會先去長篇大論去空談,先整個例子來看看!!走起....
#include <iostream>
#include <string>
using namespace std;

class A
{
public:
    A(const char*s)
    {
        cout<<s<<endl;
    }
};
class B:virtual public A
{
public:
    B(const char*s1,const char*s2):A(s1)
    {
        cout <<s2<<endl;
    }
};

class C:virtual public A
{
public:
    C(const char*s1,const char*s2):A(s1)
    {
        cout<<s2<<endl;
    }
};

class D:public B,C
{
public:
    D(const char *s1,const char *s2,const char*s3,const char*s4):B(s1,s2),C(s1,s3),A(s1)
    {
        cout <<s4<<endl;
    }
};
int main(int argc, char* argv[])
{
    D *ptr = new D("class A","class B","class C","class D");
    delete ptr;
    ptr = NULL;
    return 0;
}

先不要忙着去執行代碼!!

來看幾個基本概念:

一、虛基類的作用:

    當一個類的部分或者全部基類來自另一個共同的基類時,這些直接基類中從上一級共同基類繼承來的  就擁有相同的名稱。在派生類的對象中,這些同名數據成員在內存中同時擁有多個拷貝,同一個函數名會有多個映射。我們可以使用作用域分蝙蝠來唯一標識並分別訪問他們,也可以將共同基類設置為虛基類,這時從不同的路徑繼承過來的同名數據成員在內存中就只用一個拷貝,同一個函數名也只有一個映射。

二、虛基類的聲明  語法形式:

    class 派生類名:virtual  繼承方式  基類名

三、使用虛基類時應該注意:

  1>一個類可以在一個類族中用作虛基類,也可以用作非虛基類。

  2>在派生類的對象中,同名的虛基類只產生一個虛基類子對象,而某個非虛基類產生各自的對象。

  3>虛基類子對象是由最派生類(最后派生出來的類)的構造函數通過調用虛基類的構造函數進行初始化。

  4>最派生類是指在繼承類結構中建立對象時所指定的類。

  5>在派生類的構造函數的成員初始化列表中,必須列出對虛基類構造函數的調用,如果沒有列出,則表示使用該虛基類的缺省構造函數。

  6>在虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中,都要列出對虛基類構造函數的調用。但只有用於建立對象的最派生類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類構造函數的調用在執行中被忽略,從而保證對虛基類子對象只初始化一次。

  7>在一個成員初始化列表中,同時出現對虛基類和非虛基類構造函數的調用時,基類的構造函數先於非虛基類的構造函數執行。

  8>虛基類並不是在聲明基類時聲明的,而是在聲明派生類是,指定繼承方式時聲明的。因為一個基類可以在生成一個派生類作為虛基類,而在生成另一個派生類時不作為虛基類。

  溫馨提示:使用多重繼承時要十分小心,經常會出現二義性。許多專業人員認為:不要提倡在程序中使用多重繼承,只有在比較簡單和不易出現二義性的情況或是在必要時才使用多重繼承,能用單一繼承解決的問題就不要使用多重繼承,也是由於這個原因,有些面向對象的程序設計語言,並不支持多重繼承。

   現在對虛基類構造函數了解了沒??如果還不了解那么咱們就繼續深入研究.....

首先,要知道虛擬繼承與普通繼承的區別:

假設derived繼承自base類,那么derived與base是一種“is a”的關系,即derived類是base類,而反之錯誤;

假設derived虛繼承自base類,那么derived與base是一種“has a”的關系,即derived類有一個指向base類的vptr。

  因此虛繼承可以認為不是一種繼承關系,而可以認為是一種組合的關系。因為虛繼承有着“繼承”兩個關鍵字,那么大部分人都認為虛繼承與普通繼承的用法沒有什么太大的不同,由此用在繼承體系中,這種將虛繼承認為是普通繼承的危害更加大!先用一個例子來說明問題:

#include <iostream>
using namespace std;

class base
{
public:
    base()
    {
        cout <<"base::base()!"<<endl;
    }
    void printBase()
    {
        cout<<"base::printBase()!"<<endl;
    }
};

class derived:public base
{
public:
    derived()
    {
        cout<<"derived::derived()!"<<endl;
    }
    void printDerived()
    {
        cout<<"derived::printDerived()!"<<endl;
    }
};

int main(int argc, char* argv[])
{

    derived oo;
    base oo1(static_cast<base>(oo));

    oo1.printBase();

    cout <<"---------------------"<<endl;
    derived oo2= static_cast<derived&>(oo1);
    oo2.printDerived();
}

運行結果:

對前面的例子稍加修改......................

#include <iostream>
using namespace std;

class base1
{
public:
    base1()
    {
        cout<<"base::base()!"<<endl;
    }
    void printBase()
    {
        cout<<"base::printBase()!"<<endl;
    }
};
class derived1:virtual public base1
{
public:
    derived1()
    {
        cout<<"derived::derived()!"<<endl;
    }
    void printDerived()
    {
        cout <<"derived::printDerived()!"<<endl ;
    }

};

int main(int argc, char* argv[])
{
    derived1 oo;
    base1 oo1(static_cast<base1>(oo));
    oo1.printBase();
    
    derived1 oo2 = static_cast<derived1&>(oo1);
    oo2.printDerived();
    return 0;
}

 

會發現編譯錯誤:error C2635: cannot convert a 'base1*' to a 'derived1*'; conversion from a virtual base class is implied(代碼中紅色部分出錯)

可以看到不能將基類通過static_cast轉換為繼承類。我們知道c++提供的強制轉換函數static_cast對於繼承體系中的類對象的轉換一般是可行的。那么這里為什么不可以呢??

virtual base class的原始模型是在class object中為每一個有關聯的virtual base class加上一個指針vptr,該指針指向virtual基類表。有的編譯器是在繼承類已存在的virtual table直接擴充導入一個virtual base class table。不管怎么樣由於虛繼承已完全破壞了繼承體系,不能按照平常的繼承體系來進行類型轉換。

 

  • 我們清楚了虛基類構造函數是怎么回事,那么接下來講解一下虛基類構造函數調用順序!!

我們下來了解虛擬繼承中遇到最廣泛的菱形結構:

#include <iostream>
using namespace std;

class stream
{
public:
    stream()
    {
        cout <<"stream::stream()!"<<endl;
    }
};


class iistream:virtual stream
{
public:
    iistream()
    {
        cout <<"istream::istream()!"<<endl;
    }
};

class oostream:virtual stream
{
public:
    oostream()
    {
        cout <<"ostream::ostream()!"<<endl;
    }
};

class iiostream:public iistream,oostream
{
public:
    iiostream()
    {
        cout<<"iiostream::iiostream()!"<<endl;
    }
};

int main(int argc, char* argv[])
{
    iiostream oo;
    return 0;
}

 

運行結果:

本來虛擬繼承的目的就是當多重繼承出現重復的基類時,其只保存一份基類,減少內存開銷。

 

這樣子的菱形結構,使公共基類只產生一個拷貝。

從基類stream派生新類時,使用virtual將類stream說明為虛基類,這時派生類istream、ostream包含一個指向虛基類的vptr,而不會產生實際的stream空間。所以最終iiostream也含有一個指向虛基類的vptr,調用stream中的成員方法時,通過vptr去調用,不會產生二義性!

現在我們換種方式使用虛繼承:

#include <iostream>
using namespace std;

class stream
{
public:
    stream()
    {
        cout <<"stream::stream()!"<<endl;
    }
};


class iistream:public stream
{
public:
    iistream()
    {
        cout <<"istream::istream()!"<<endl;
    }
};

class oostream:public stream
{
public:
    oostream()
    {
        cout <<"ostream::ostream()!"<<endl;
    }
};

class iiostream:virtual iistream,oostream
{
public:
    iiostream()
    {
        cout<<"iiostream::iiostream()!"<<endl;
    }
};

int main(int argc, char* argv[])
{
    iiostream oo;
    return 0;
}

運行結果:

從結果上可以看到,其構造過程中重復出現基類的stream的構造過程,這樣就完全沒有達到虛擬繼承的目的。其繼承結構為:

從繼承結構可以看出,如果iiostream對象調用基類stream重的成員方法,會導致方法的二義性。因為iiostream含有指向其虛繼承基類istream,ostream的vptr。而istream,ostream包含了stream的空間,所以導致iiostream不知道導致時調用那個stream的方法。要解決該問題,即在調用成員方法時需要加上作用域!

 

后續

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM