日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++虚继承(七) --- 虚继承对基类构造函数调用顺序的影响

發布時間:2024/4/11 c/c++ 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++虚继承(七) --- 虚继承对基类构造函数调用顺序的影响 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

繼承作為面向對象編程的一種基本特征,其使用頻率非常高。而繼承包含了虛擬繼承和普通繼承,在可見性上分為public、protected、private。可見性繼承比較簡單,而虛擬繼承對學習c++的難度較大。

首先,虛擬繼承與普通繼承的區別有:

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

假設derived 虛繼承自base類,那么derivd與base是一種“has a”的關系,即derived類有一個指向base類的vptr。(貌似有些牽強!某些編譯器確實如此,關于虛繼承與普通繼承的差異見:c++ 虛繼承與繼承的差異?)

因此虛繼承可以認為不是一種繼承關系,而可以認為是一種組合的關系。正是因為這樣的區別,下面我們針對虛擬繼承來具體分析。虛擬繼承中遇到最廣泛的是菱形結構。下面從菱形虛繼承結構說起吧:

[cpp]?view plaincopy
  • 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,public?oostream??
  • {??
  • public:??
  • ????iiostream(){cout<<"iiostream::iiostream()!"<<endl;}??
  • };??
  • ??
  • int?main(int?argc,?const?char?*?argv[])??
  • {??
  • ????iiostream?oo;??
  • }??

  • 程序運行的輸出結果為:

    stream::stream()!

    istream::istream()!

    ostream::ostream()!

    iiostream::iiostream()!???

    輸出這樣的結果是毫無懸念的!本來虛擬繼承的目的就是當多重繼承出現重復的基類時,其只保存一份基類。減少內存開銷。其繼承結構為:

    ? ? ? ? ? ??stream?

    ???????????/??? ? ? ? ? ???\???

    ?????istream???ostream???

    ???????????\??? ? ? ? ? ? ???/

    ???????????iiostream??

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

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

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

    [cpp]?view plaincopy
  • 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,virtual?oostream??
  • {??
  • public:??
  • ????iiostream(){cout<<"iiostream::iiostream()!"<<endl;}??
  • };??
  • ??
  • int?main(int?argc,?const?char?*?argv[])??
  • {??
  • ????iiostream?oo;??
  • }??


  • 其輸出結果為:

    stream::stream()!

    istream::istream()!

    stream::stream()!

    ostream::ostream()!

    iiostream::iiostream()!

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

    stream????????? ? ??stream????????????????????????????????????????????????????????????????????

    ????????????\????????? ? ? ? ? ???/???????????????????????????????

    ???istream????ostream??????????????????????????????????????

    ?????????????\?????? ? ? ? ? ????/?????????????????????????????????????????????????????????????

    ??????????????iiostream??

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

    [cpp]?view plaincopy
  • class?stream??
  • {??
  • ???void?f(){cout<<"here!"<<endl;}??
  • }????
  • main()??
  • {??
  • ??iiostream?ii;??
  • ??ii.f();??
  • }??

  • 編譯器提示調用f方法錯誤。而采用

    [cpp]?view plaincopy
  • ii.istream::f();??

  • 編譯通過,并且會調用istream類vptr指向的f()方法。 前面說了這么多,在實際的應用中虛擬繼承的胡亂使用,更是會導致繼承順序以及基類構造順序的混亂。如下面的代碼:

    [cpp]?view plaincopy
  • class?B1??
  • {??
  • public:??
  • ????B1(){cout<<"B1::B1()!<"<<endl;}??
  • ????void?f()?{cout<<"i'm?here!"<<endl;}??
  • };??
  • ??
  • class?V1:?public?B1??
  • {??
  • public:??
  • ????V1(){cout<<"V1::V1()!<"<<endl;}??
  • };??
  • ??
  • class?D1:?virtual?public?V1??
  • {??
  • public:??
  • ????D1(){cout<<"D1::D1()!<"<<endl;}??
  • };??
  • ??
  • class?B2??
  • {??
  • public:??
  • ????B2(){cout<<"B2::B2()!<"<<endl;}??
  • };??
  • ??
  • class?B3??
  • {??
  • public:??
  • ????B3(){cout<<"B3::B3()!<"<<endl;}??
  • };??
  • ??
  • class?V2:public?B1,?public?B2??
  • {??
  • public:??
  • ????V2(){cout<<"V2::V2()!<"<<endl;}??
  • };??
  • ??
  • class?D2:virtual?public?V2,?public?B3??
  • {??
  • public:??
  • ????D2(){cout<<"D2::D2()!<"<<endl;}??
  • };??
  • ??
  • class?M1??
  • {??
  • public:??
  • ????M1(){cout<<"M1::M1()!<"<<endl;}??
  • };??
  • ??
  • class?M2??
  • {??
  • public:??
  • ????M2(){cout<<"M2::M2()!<"<<endl;}??
  • };??
  • ??
  • class?X:public?D1,?public?D2??
  • {??
  • ????M1?m1;??
  • ????M2?m2;??
  • };??
  • int?main(int?argc,?const?char?*?argv[])??
  • {??
  • ????X?x;??
  • }??


  • 上面的代碼是來自《Exceptional C++ Style》中關于繼承順序的一段代碼。可以看到,上面的代碼繼承關系非常復雜,而且層次不是特別的清楚。而虛繼承的加入更是讓繼承結構更加無序。不管怎么樣,我們還是可以根據c++的標準來分析上面代碼的構造順序。c++對于創建一個類類型的初始化順序是這樣子的:

    1.最上層派生類的構造函數負責調用虛基類子對象的構造函數。所有虛基類子對象會按照深度優先、從左到右的順序進行初始化;

    2.直接基類子對象按照它們在類定義中聲明的順序被一一構造起來;

    3.非靜態成員子對象按照它們在類定義體中的聲明的順序被一一構造起來;

    4.最上層派生類的構造函數體被執行。

    根據上面的規則,可以看出,最先構造的是虛繼承基類的構造函數,并且是按照深度優先,從左往右構造。因此,我們需要將繼承結構劃分層次。顯然上面的代碼可以認為是4層繼承結構。其中最頂層的是B1,B2類。第二層是V1,V2,V3。第三層是D1,D2.最底層是X。而D1虛繼承V1,D2虛繼承V2,且D1和D2在同一層。所以V1最先構造,其次是V2.在V2構造順序中,B1先于B2.虛基類構造完成后,接著是直接基類子對象構造,其順序為D1,D2.最后為成員子對象的構造,順序為聲明的順序。構造完畢后,開始按照構造順序執行構造函數體了。所以其最終的輸出結果為:

    B1::B1()!<

    V1::V1()!<

    B1::B1()!<

    B2::B2()!<

    V2::V2()!<

    D1::D1()!<

    B3::B3()!<

    D2::D2()!<

    M1::M1()!<

    M2::M2()!<

    從結果也可以看出其構造順序完全符合上面的標準。而在結果中,可以看到B1重復構造。還是因為沒有按照要求使用virtual繼承導致的結果。要想只構造B1一次,可以將virtual全部改在B1上,如下面的代碼:

    [cpp]?view plaincopy
  • class?B1??
  • {??
  • public:??
  • ????B1(){cout<<"B1::B1()!<"<<endl;}??
  • ????void?f()?{cout<<"i'm?here!"<<endl;}??
  • };??
  • ??
  • class?V1:?virtual?public?B1???//public修改為virtual??
  • {??
  • public:??
  • ????V1(){cout<<"V1::V1()!<"<<endl;}??
  • };??
  • ??
  • class?D1:??public?V1??
  • {??
  • public:??
  • ????D1(){cout<<"D1::D1()!<"<<endl;}??
  • };??
  • ??
  • class?B2??
  • {??
  • public:??
  • ????B2(){cout<<"B2::B2()!<"<<endl;}??
  • };??
  • ??
  • class?B3??
  • {??
  • public:??
  • ????B3(){cout<<"B3::B3()!<"<<endl;}??
  • };??
  • ??
  • class?V2:virtual?public?B1,?public?B2?//public?B1修改為virtual?public?B1??
  • {??
  • public:??
  • ????V2(){cout<<"V2::V2()!<"<<endl;}??
  • };??
  • ??
  • class?D2:?public?V2,?public?B3??
  • {??
  • public:??
  • ????D2(){cout<<"D2::D2()!<"<<endl;}??
  • };??
  • ??
  • class?M1??
  • {??
  • public:??
  • ????M1(){cout<<"M1::M1()!<"<<endl;}??
  • };??
  • ??
  • class?M2??
  • {??
  • public:??
  • ????M2(){cout<<"M2::M2()!<"<<endl;}??
  • };??
  • ??
  • class?X:public?D1,?public?D2??
  • {??
  • ????M1?m1;??
  • ????M2?m2;??
  • };??


  • 根據上面的代碼,其輸出結果為:

    B1::B1()!<

    V1::V1()!<

    D1::D1()!<

    B2::B2()!<

    V2::V2()!<

    B3::B3()!<

    D2::D2()!<

    M1::M1()!<

    M2::M2()!<

    由于虛繼承導致其構造順序發生比較大的變化。不管怎么,分析的規則還是一樣。

    上面分析了這么多,我們知道了虛繼承有一定的好處,但是虛繼承會增大占用的空間。這是因為每一次虛繼承會產生一個vptr指針。空間因素在編程過程中,我們很少考慮,而構造順序卻需要小心,因此使用未構造對象的危害是相當大的。因此,我們需要小心的使用繼承,更要確保在使用繼承的時候保證構造順序不會出錯。下面我再著重強調一下基類的構造順序規則:

    1.最上層派生類的構造函數負責調用虛基類子對象的構造函數。所有虛基類子對象會按照深度優先、從左到右的順序進行初始化;

    2.直接基類子對象按照它們在類定義中聲明的順序被一一構造起來;

    3.非靜態成員子對象按照它們在類定義體中的聲明的順序被一一構造起來;

    4.最上層派生類的構造函數體被執行。

    總結

    以上是生活随笔為你收集整理的C++虚继承(七) --- 虚继承对基类构造函数调用顺序的影响的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。