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

歡迎訪問 生活随笔!

生活随笔

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

c/c++

C++虚继承(九) --- 构造函数调用顺序的实用之处

發(fā)布時間:2024/4/11 c/c++ 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++虚继承(九) --- 构造函数调用顺序的实用之处 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

虛擬繼承是C++語言中一個非常重要但是又比較生僻的存在,它的定義非常簡單,但是對于理解C++的繼承機制卻是非常有用的。筆者最近學習過程中發(fā)現(xiàn)對C++的虛擬繼承不是很明朗,故在這里對虛繼承做個小結。

首先說下遇到的問題吧。代碼如下(代碼來自于何海濤《程序員面試精選100題第32題)。意圖是要設計一個不能被繼承的類,類似java中的final。但是又可以生成棧對象,可以像一般的C++類一樣使用。

[cpp]?view plain?copy ?
  • #include?"stdafx.h"??
  • #include?<iostream>??
  • using?namespace?std;??
  • template?<class?T>?class?MakeFinal??
  • {??
  • ????friend?T;??
  • private:??
  • ????MakeFinal()??
  • ????{??
  • ????????cout<<"in?MakeFinal"<<endl;??
  • ????}??
  • ????~MakeFinal(){}??
  • };??
  • class?FinalClass:?virtual?public?MakeFinal<FinalClass>??
  • {??
  • public:??
  • ????FinalClass()??
  • ????{??
  • ????????cout<<"in?FinalClass"<<endl;??
  • ????}??
  • ????~FinalClass(){}??
  • };??
  • ??
  • class?Try:?public?FinalClass??
  • {??
  • public:??
  • ????Try()??
  • ????{??
  • ????????cout<<"in?Try"<<endl;??
  • ????}??
  • };??
  • 這樣的確使得FinalClass不能被繼承了,原因在于類FinalClass是從類MakeFinal<Final>虛繼承過來的,在調用Try的構造函數(shù)的時候,會直接跳過FinalClass而直接調用MakeFinal<FinalClass>的構造函數(shù)。而Try不是MakeFinal<Final>的友元,所以這里就會出現(xiàn)編譯錯誤。但是如果把虛繼承改成一般的繼承,這里就沒什么問題了。筆者對這里的調用順序不是很明朗,為了對虛繼承有徹底的了解,故做個小結。將從下面幾個方向進行總結:1、為何要有虛繼承;2、虛繼承對于類的對象布局的影響;3、虛基類對構造函數(shù)的影響;

    1、為什么需要虛繼承

    由于C++支持多重繼承,那么在這種情況下會出現(xiàn)重復的基類這種情況,也就是說可能出現(xiàn)將一個類兩次作為基類的可能性。比如像下面的情況

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected?Base??
  • {??
  • public:??
  • ????DerivedA()??
  • ????{??
  • ????????cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB:?protected?Base??
  • {??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?MyClass:DerivedA,DerivedB??
  • {??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • 編譯時的錯誤如下


    這中情況下會造成在MyClass中訪問value時出現(xiàn)路徑不明確的編譯錯誤,要訪問數(shù)據(jù),就需要顯示地加以限定。變成DerivedA::value或者DerivedB::value,以消除歧義性。并且,通常情況下,像Base這樣的公共基類不應該表示為兩個分離的對象,而要解決這種問題就可以用虛基類加以處理。如果使用虛繼承,編譯便正常了,類的結構示意圖便如下。

    虛繼承的特點是,在任何派生類中的virtual基類總用同一個(共享)對象表示,正是如上圖所示。

    2、虛繼承對類的對象布局的影響

    要理解多重繼承情況中重復基類時為什么會出現(xiàn)訪問路徑不明確的編譯錯誤,需要了解繼承中類對象在內存中的布局。在C++繼承中,子類會繼承父類的成員變量,因此在子類對象在內存中會包括來自父類的成員變量。實例代碼如下,輸出結果表明了每個對象在內存中所占的大小。

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????//cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected??Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????//cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB:?protected??Base??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????//cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?MyClass:DerivedA??
  • {??
  • private:??
  • ????int?my_value;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????Base?base_obj;??
  • ????DerivedA?derA_obj;??
  • ????MyClass?my_obj;??
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;??
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;??
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;??
  • }??
  • 輸出結果如下

    從類的定義結合這里的輸出便不難明白,在子類對象中是包含了父類數(shù)據(jù)的,即在C++繼承中,一個子類的object所表現(xiàn)出來的東西,是其自己的members加上其基類的member的總和。示意圖如下(這里只討論非靜態(tài)變量)


    在單繼承的時候,訪問相關的數(shù)據(jù)成員時,只需要使用名字即可。但是,在多重繼承時,情況會變得復雜。因為重復基類中,在子類中變量名是相同的。這時,如果直接使用名字去訪問,便會出現(xiàn)歧義性。看下面的代碼以及對應的輸出

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????//cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected??Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????//cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB:?protected??Base??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????//cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?MyClass:DerivedA,DerivedB??
  • {??
  • private:??
  • ????int?my_value;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????Base?base_obj;??
  • ????DerivedA?derA_obj;??
  • ????MyClass?my_obj;??
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;??
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;??
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;??
  • }??
  • 輸出如下


    代碼的變化之處在于MyClass同時繼承了DerivedA和DerivedB。而my_obj在內存中的大小變成了20,比之前大了8.正好是增加了繼承至DerivedB中的數(shù)據(jù)部分的大小。上面情況中,my_obj在內存中的布局示意圖如下



    從圖中可以看到,來自Base基類的數(shù)據(jù)成員value重復出現(xiàn)了兩次。這也正是為什么在MyClass中直接訪問value時會出現(xiàn)訪問不明確的問題了。

    那么使用虛繼承后,對象的數(shù)據(jù)在內存中的布局又是什么樣子呢?按照預測,既然在my_obj中只有一份來自Base的value,那么大小是否就是16呢?

    代碼及輸出如下

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????//cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected??virtual?Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????//cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB:?protected?virtual?Base??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????//cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?MyClass:DerivedA,DerivedB??
  • {??
  • private:??
  • ????int?my_value;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????Base?base_obj;??
  • ????DerivedA?derA_obj;??
  • ????DerivedB?derB_obj;??
  • ????MyClass?my_obj;??
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;??
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;??
  • ????cout<<"size?of?DerivedB?object?"<<sizeof(derB_obj)<<endl;??
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;??
  • };??
  • 輸出結果如下


    可以看到,DerivedA和DerivedB對象的大小變成了12,而MyClass對象的大小則變成了24.似乎大大超出了我們的預料。這其實是由于編譯器在其中插入了一些東西用來尋找這個共享的基類數(shù)據(jù)所用而造成的。(來自《深度探索C++對象模型》第3章 侯捷譯)這樣理解,Class如果內含一個或多個虛基類子對象,那么將被分割為兩部分:一個不變部分和一個共享部分。不變局部中的數(shù)據(jù),不管后繼如何衍化,總是擁有固定的offset,所以這一部分數(shù)據(jù)可以直接存取。至于共享局部,所表現(xiàn)的就是虛基類子對象。根據(jù)編譯其的不同,會有不同的方式去得到這部分的數(shù)據(jù),但總體來說都是需要有一個指向這部分共享數(shù)據(jù)的指針。

    示意圖如下


    當然實際編譯器使用的技術比這個要復雜,這里就不做詳細討論了。感興趣的朋友可以參見《深入探索C++對象模型》

    3、虛繼承對構造函數(shù)的影響

    對于構造函數(shù)的影響,借助于下面的原則可以理解(來自《深入理解C++對象模型》)

    構造函數(shù)的調用可能內帶大量的隱藏碼,因為編譯器會對構造函數(shù)進行擴充,一般而言編譯器所作的擴充操作大約如下:

    1、記錄在成員初始化列表中的數(shù)據(jù)成員的初始化操作會被放到構造函數(shù)本身中,按照數(shù)據(jù)成員聲明的順序

    2、如果有一個數(shù)據(jù)成員沒有出現(xiàn)在初始化列表中,但是它有一個默認構造函數(shù),那么這個默認構造函數(shù)會被調用

    3、在那之前,如果有虛函數(shù)表,會調整虛函數(shù)表指針

    4、在那之前,會對上一層基類的構造函數(shù)進行調用

    5、在那之前,所有虛基類的構造函數(shù)必須被調用,按照聲明的繼承順序從左往右,從最深到最淺的順序


    從上面的規(guī)則可以看出,對于虛基類的構造函數(shù)的調用是放在最前面的,并且需要子類對于虛基類的構造函數(shù)擁有訪問權限

    從下面的示例代碼可以看出

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected??Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?TestClass??
  • {??
  • public:??
  • ????TestClass()??
  • ????{??
  • ????????cout<<"in?TestClass"<<endl;??
  • ????}??
  • };??
  • class?MyClass:DerivedA,virtual?DerivedB??
  • {??
  • private:??
  • ????int?my_value;??
  • ????TestClass?testData;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????/*?
  • ????Base?base_obj;?
  • ????DerivedA?derA_obj;?
  • ????DerivedB?derB_obj;?
  • ????MyClass?my_obj;?
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;?
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;?
  • ????cout<<"size?of?DerivedB?object?"<<sizeof(derB_obj)<<endl;?
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;*/??
  • ????MyClass?my_obj;??
  • }??
  • 代碼運行后的效果如下所示


    雖然在聲明繼承順序的時候DerivedA的順序是在DerivedB的前面的,但是由于DerivedB是虛擬繼承,所以對DerivedB的調用會在最前。但是如果將DerivedA繼承也改成虛繼承,那么調用順序就會發(fā)生變化。并且具體DerivedA的構造函數(shù)的調用與DerivedB的構造函數(shù)的調用順序還與是MyClass虛繼承DerivedA還是DerivedA虛繼承Base有關。還是用代碼來說明

    MyClass虛繼承DerivedA的情況,由于DerivedA聲明在DerivedB前面,并且都是虛繼承,所以先調用DerivedA的構造函數(shù)。但DerivedA的父類是Base,所以具體的構造函數(shù)的調用順序是Base、DerivedA、DerivedB、TestClass。這種情況代碼如下

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected??Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?TestClass??
  • {??
  • public:??
  • ????TestClass()??
  • ????{??
  • ????????cout<<"in?TestClass"<<endl;??
  • ????}??
  • };??
  • class?MyClass:virtual?DerivedA,virtual?DerivedB??
  • {??
  • private:??
  • ????int?my_value;??
  • ????TestClass?testData;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????/*?
  • ????Base?base_obj;?
  • ????DerivedA?derA_obj;?
  • ????DerivedB?derB_obj;?
  • ????MyClass?my_obj;?
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;?
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;?
  • ????cout<<"size?of?DerivedB?object?"<<sizeof(derB_obj)<<endl;?
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;*/??
  • ????MyClass?my_obj;??
  • }??
  • 輸出如下



    但如果將虛繼承放在由DerivedA虛繼承Base,而MyClass非虛繼承DerivedA。那么按照從左往右,從深到淺的順序,調用順序應該是Base、DerivedB、DerivedA、TestClass

    ,代碼示例如下

    [cpp]?view plain?copy ?
  • #include<iostream>??
  • using?std::cout;??
  • using?std::endl;??
  • class?Base??
  • {??
  • protected:??
  • ????int?value;??
  • public:??
  • ????Base()??
  • ????{??
  • ????????cout<<"in?Base"<<endl;??
  • ????}??
  • };??
  • class?DerivedA:protected?virtual??Base??
  • {??
  • protected:??
  • ????int?valueA;??
  • public:???
  • ????DerivedA()??
  • ????{??
  • ????????cout<<"in?DerivedA"<<endl;??
  • ????}??
  • };??
  • class?DerivedB??
  • {??
  • protected:??
  • ????int?valueB;??
  • public:??
  • ????DerivedB()??
  • ????{??
  • ????????cout<<"in?DerivedB"<<endl;??
  • ????}??
  • };??
  • class?TestClass??
  • {??
  • public:??
  • ????TestClass()??
  • ????{??
  • ????????cout<<"in?TestClass"<<endl;??
  • ????}??
  • };??
  • class?MyClass:?DerivedA,virtual?DerivedB??
  • {??
  • private:??
  • ????int?my_value;??
  • ????TestClass?testData;??
  • public:??
  • ????MyClass()??
  • ????{??
  • ????????//cout<<"in?MyClass"<<value<<endl;??
  • ????}??
  • };??
  • int?main()??
  • {??
  • ????/*?
  • ????Base?base_obj;?
  • ????DerivedA?derA_obj;?
  • ????DerivedB?derB_obj;?
  • ????MyClass?my_obj;?
  • ????cout<<"size?of?Base?object?"<<sizeof(base_obj)<<endl;?
  • ????cout<<"size?of?DerivedA?object?"<<sizeof(derA_obj)<<endl;?
  • ????cout<<"size?of?DerivedB?object?"<<sizeof(derB_obj)<<endl;?
  • ????cout<<"size?of?MyClass?object?"<<sizeof(my_obj)<<endl;*/??
  • ????MyClass?my_obj;??
  • }??
  • 輸出如下


    這里還有一個問題是,左右順序和深淺順序該如何抉擇呢?答案是先左右,再深淺。比如將上面的代碼改為class MyClas: virutal DerivedB, DerivedA{...}

    那么最后的調用順序就是DerivedB,Base, DerivedA,TestClass。讀者可以自己驗證。


    總結

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

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

    主站蜘蛛池模板: 内地级a艳片高清免费播放 91在线精品一区二区 | 香蕉av在线播放 | 三大队在线观看 | 中文字幕天堂av | 最新福利视频 | 偷拍中国夫妇高潮视频 | 国产精品无码午夜福利 | 国产真实乱人偷精品人妻 | 美女超碰| 国产黄色影视 | 操女人视频网站 | 少妇户外露出[11p] | 今天高清视频在线观看视频 | 久久国产精品国语对白 | 成年人高清视频 | 国产小视频91 | 亚洲美女色 | 日韩精品一区二区在线 | 中文字幕二区在线观看 | 久久aaa | 性久久久久久久久久久久 | 97久久精品人人澡人人爽 | 在线视频观看一区二区 | 日韩欧美国产另类 | 中文字幕人妻熟女人妻a片 麻豆91视频 | av永久网站 | 性xxxxxxxxx| 欧美特黄一级大片 | 日本激情小视频 | 日本电影大尺度免费观看 | 水蜜桃av在线 | 国产在线视频卡一卡二 | 女女高潮h冰块play失禁百合 | 国产精品27p| 91免费版黄色 | 欧美精品成人在线 | 免费啊v在线观看 | 亚洲三级小视频 | 男女插插网站 | 亚洲激情a| 国产福利小视频在线观看 | 丝袜一区二区三区 | 亚洲午夜精品一区二区 | 国产精品美女久久久久av超清 | 性久久久久久久久久 | 日韩久久精品电影 | 国产精品zjzjzj在线观看 | 欧美撒尿777hd撒尿 | 国产偷人妻精品一区二区在线 | 日韩在线视频网址 | 日本在线视频免费 | 欧美一级不卡视频 | 怎么可能高潮了就结束漫画 | 欧美性猛交ⅹxx乱大交 | 色综合中文字幕 | 一级黄色片免费播放 | 在线观看视频色 | 成人动作片 | 久久国产秒 | 久久九色 | 天天射网 | 爱搞逼综合网 | 少妇人妻偷人精品一区二区 | 青青草www| 日韩毛片无码永久免费看 | 天堂а√在线中文在线 | 一边吃奶一边摸做爽视频 | 玉足调教丨vk24分钟 | 国产秋霞| 欧美精品在线观看 | 亚洲综合一区中 | 激情视频91 | 91男女视频| 国产欧美日韩中文字幕 | 99re久久精品国产 | 欧美色射 | 亚洲中文字幕在线观看 | 激情xxxx| 国产成人精品一区二区三区在线 | 国产高清毛片 | 日韩在线观看一区二区 | 成人av一区二区在线观看 | 日韩视频网址 | 日本三级吃奶头添泬 | 欧美36p| 亚洲精品免费电影 | 91大神视频在线播放 | 娇妻之欲海泛舟无弹窗笔趣阁 | 亚洲人成人 | 日本少妇裸体做爰 | 男人扒女人添高潮视频 | 中文字幕a级片 | 国产视频你懂的 | 伊人手机视频 | 风韵丰满熟妇啪啪区老熟熟女 | www.插插插.com | 丰满人妻综合一区二区三区 | 一本大道av伊人久久综合 | 免费jizz|