1. 程式人生 > >C++虛繼承(九) --- 建構函式呼叫順序的實用之處

C++虛繼承(九) --- 建構函式呼叫順序的實用之處

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

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

  1. #include "stdafx.h"
  2. #include <iostream>
  3. usingnamespace std;  
  4. template <
    class T> class MakeFinal  
  5. {  
  6.     friend T;  
  7. private:  
  8.     MakeFinal()  
  9.     {  
  10.         cout<<"in MakeFinal"<<endl;  
  11.     }  
  12.     ~MakeFinal(){}  
  13. };  
  14. class FinalClass: virtualpublic MakeFinal<FinalClass>  
  15. {  
  16. public:  
  17.     FinalClass()  
  18.     {  
  19.         cout<<"in FinalClass"
    <<endl;  
  20.     }  
  21.     ~FinalClass(){}  
  22. };  
  23. class Try: public FinalClass  
  24. {  
  25. public:  
  26.     Try()  
  27.     {  
  28.         cout<<"in Try"<<endl;  
  29.     }  
  30. };  

這樣的確使得FinalClass不能被繼承了,原因在於類FinalClass是從類MakeFinal<Final>虛繼承過來的,在呼叫Try的建構函式的時候,會直接跳過FinalClass而直接呼叫MakeFinal<FinalClass>的建構函式。而Try不是MakeFinal<Final>的友元,所以這裡就會出現編譯錯誤。但是如果把虛繼承改成一般的繼承,這裡就沒什麼問題了。筆者對這裡的呼叫順序不是很明朗,為了對虛繼承有徹底的瞭解,故做個小結。將從下面幾個方向進行總結:1、為何要有虛繼承;2、虛繼承對於類的物件佈局的影響;3、虛基類對建構函式的影響;

1、為什麼需要虛繼承

由於C++支援多重繼承,那麼在這種情況下會出現重複的基類這種情況,也就是說可能出現將一個類兩次作為基類的可能性。比如像下面的情況

  1. #include<iostream>
  2. using std::cout;  
  3. using std::endl;  
  4. class Base  
  5. {  
  6. protected:  
  7.     int value;  
  8. public:  
  9.     Base()  
  10.     {  
  11.         cout<<"in Base"<<endl;  
  12.     }  
  13. };  
  14. class DerivedA:protected Base  
  15. {  
  16. public:  
  17.     DerivedA()  
  18.     {  
  19.         cout<<"in DerivedA"<<endl;  
  20.     }  
  21. };  
  22. class DerivedB: protected Base  
  23. {  
  24. public:  
  25.     DerivedB()  
  26.     {  
  27.         cout<<"in DerivedB"<<endl;  
  28.     }  
  29. };  
  30. class MyClass:DerivedA,DerivedB  
  31. {  
  32. public:  
  33.     MyClass()  
  34.     {  
  35.         cout<<"in MyClass"<<value<<endl;  
  36.     }  
  37. };  

編譯時的錯誤如下


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

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

2、虛繼承對類的物件佈局的影響

要理解多重繼承情況中重複基類時為什麼會出現訪問路徑不明確的編譯錯誤,需要了解繼承中類物件在記憶體中的佈局。在C++繼承中,子類會繼承父類的成員變數,因此在子類物件在記憶體中會包括來自父類的成員變數。例項程式碼如下,輸出結果表明了每個物件在記憶體中所佔的大小。

  1. #include<iostream>
  2. using std::cout;  
  3. using std::endl;  
  4. class Base  
  5. {  
  6. protected:  
  7.     int value;  
  8. public:  
  9.     Base()  
  10.     {  
  11.         //cout<<"in Base"<<endl;
  12.     }  
  13. };  
  14. class DerivedA:protected  Base  
  15. {  
  16. protected:  
  17.     int valueA;  
  18. public:   
  19.     DerivedA()  
  20.     {  
  21.         //cout<<"in DerivedA"<<endl;
  22.     }  
  23. };  
  24. class DerivedB: protected  Base  
  25. {  
  26. protected:  
  27.     int valueB;  
  28. public:  
  29.     DerivedB()  
  30.     {  
  31.         //cout<<"in DerivedB"<<endl;
  32.     }  
  33. };  
  34. class MyClass:DerivedA  
  35. {  
  36. private:  
  37.     int my_value;  
  38. public:  
  39.     MyClass()  
  40.     {  
  41.         //cout<<"in MyClass"<<value<<endl;
  42.     }  
  43. };  
  44. int main()  
  45. {  
  46.     Base base_obj;  
  47.     DerivedA derA_obj;  
  48.     MyClass my_obj;  
  49.     cout<<"size of Base object "<<sizeof(base_obj)<<endl;  
  50.     cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;  
  51.     cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;  
  52. 相關推薦

    C++繼承() --- 建構函式呼叫順序實用

    虛擬繼承是C++語言中一個非常重要但是又比較生僻的存在,它的定義非常簡單,但是對於理解C++的繼承機制卻是非常有用的。筆者最近學習過程中發現對C++的虛擬繼承不是很明朗,故在這裡對虛繼承做個小結。 首先說下遇到的問題吧。程式碼如下(程式碼來自於何海濤《程式設計師

    關於C++派生類中建構函式呼叫順序的問題

        近期在網上搜尋了一下關於C++派生類建構函式的呼叫問題,大部分博文給出的順序是: 1、基類構造 2、成員構造 3、派生構造。 這個順序嚴格來講是錯誤的,對新手極具誤導性!     依據侯捷翻譯的Stanley B. Lippman的<<深度探索C++

    C++中多繼承建構函式呼叫順序

    class B1 {public: B1(int i) {cout<<"consB1"<<i<<endl;} };//定義基類B1 class B2 {public: B2(int j) {cout<<"consB2"<<

    C++多繼承建構函式呼叫順序

    class B1 {public: B1(int i) {cout<<"consB1"<<i<<endl;} };//定義基類B1 class B2 {public: B2(int j) {cout<<"co

    C++ 基類建構函式的執行順序

    (1)呼叫基類的建構函式,多個基類則按派生類宣告時           列出的次序、從左到右呼叫,而不是初始化列表中           的次序。 (2)呼叫物件成員(子物件)的建構函式,按類宣告中           物件成員出現的次序呼叫,而不是初始化列表中的    

    C#類繼承建構函式的執行序列

    不知道大家在使用繼承的過程中有木有遇到過呼叫建構函式時沒有按照我們預期的那樣執行呢?一般情況下,出現這樣的問題往往是因為類繼承結構中的某個基類沒有被正確例項化,或者沒有正確給基類建構函式提供資訊,如果理解在物件生命週期的這個階段發生的事情,將更利於解決此類問題。 為了例項化派生的類,必須先例項化它的基類。而

    C++類中一個建構函式呼叫另一個建構函式

    class A { int a; int b; int c; public: A(int aa, int bb) : a(aa), b(bb),c(0) { cout << "aa bb" << endl; } A(int aa, in

    C++繼承建構函式和解構函式呼叫順序

    class Fish:public Animal { public: Fish() { cout<<"this is Fish"<<endl; } ~Fish() { cout<<"this is free Fish"<<en

    # c++中的複合與繼承相關建構函式呼叫先後

    複合關係 #include <iostream> class A{ public: A(int m=0):a(m){ std::cout<<"base's defualt建構函式"<<std::

    總結c++類的建構函式 拷貝建構函式 解構函式 賦值運算子過載的特點以及函式呼叫順序

    對 c++類的建構函式 拷貝建構函式 解構函式 賦值運算子過載 相關知識的總結,並附上例子,希望對大家有幫助,有錯誤大家可以指出來  一 建構函式    1 建構函式: 建構函式時一個特殊的成員函式,用來初始化物件的資料成員,在物件建立時,由編譯器自動呼叫,在物件的生命週期

    C#陷阱建構函式呼叫方法

      先說問題描述,現在有一個Test的類,其資訊如下: class Test { public string info = "測試類的成員"; }定義一個人類的類資訊: cl

    C++派生類的建構函式和解構函式呼叫順序

    派生類的建構函式只負責對派生類新增的成員進行初始化,對所有從基類繼承下來的成員,其初始化工作還是由基類的建構函式完成。 派生類的資料成員是由所有基類的資料成員與派生類新增的資料成員共同組成的。如果派生類新增資料成員中包括有內嵌的其他類物件,派生類的資料成員中實際上還間接包括

    多重繼承建構函式和解構函式的執行順序(包含基類)

    C++程式碼: #include <iostream> using namespace std; class A { public: A() { co

    C++11新特性(80)-繼承建構函式與多重繼承

    複習 本文算是前面文章的繼續。 對於繼承的建構函式,C++11通過一個簡單的using語句,使得繼承一個類時可以省去一些麻煩。具體可以參照下面的文章。 C++11新特性(59)-繼承的建構函式 https://mp.weixin.qq.com/s/BGUa7-RSCtFRnBYj

    c++子類繼承父類函式呼叫特性原理以及與java的對比

    c++ c++中子類繼承了父類,子類物件的函式和變數會接著新增在父類物件的記憶體後面,以此類推。。。 如果c++中父類的那個變數或者函式宣告為virtual虛擬函式,那麼子類物件的同名函式就直接覆蓋了(即在記憶體中真正的覆蓋,父類的這個函式已經不在了)父類物件的這個函式 如

    反彙編C++ OOP程式碼 分析建構函式如何被呼叫 以及簡單的C++物件記憶體模型

    在今天進行C++程式碼的思考時,產生一個疑問,就是C++類的建構函式是如何被呼叫的 於是就做了一個簡單的實驗來驗證自己的想法。 //main.cpp #include &lt;stdio.h&gt; class People{ private: int i; i

    C++中,組合類,類的解構函式呼叫順序

    // test4.cpp : 定義控制檯應用程式的入口點。 //    1.宣告一個CPU類,包含等級rank,頻率frequency,電壓voltage等屬性,有兩個共有函式 //    stop 和ran。 // &nbs

    C++中的預設建構函式和初始化列表和子類呼叫父類建構函式

    預設建構函式:未提供顯式初始值時,用來建立物件的建構函式。 class testClass { public:     testClass();               

    C++繼承建構函式、複製控制

            每個派生類物件由派生類中定義的(非static)成員加上一個或多個基類子物件構成,因此,當構造、複製、賦值和撤銷派生型別物件時,也會構造、複製、賦值和撤銷這些基類子物件。 建構函式和複製控制成員不能繼承,每個類定義自己的建構函式和複製控制成員。像任何類一樣,

    c++中建構函式呼叫另一建構函式的問題

    今天去一家公司面試,筆試中碰到一個題,覺得有些意思,自己當時其實也沒有完全搞清楚,這裡跟大家分享一下,也算是自己做個筆記 題目如下:問下列程式碼的列印結果為0嗎? #include <stdlib.h> #include <iostre