C++ 多重繼續和虛擬繼續對象模子、效力剖析。本站提示廣大學習愛好者:(C++ 多重繼續和虛擬繼續對象模子、效力剖析)文章只能為提供參考,不一定能成為您想要的結果。以下是C++ 多重繼續和虛擬繼續對象模子、效力剖析正文
1、多態
C++多態經由過程繼續和靜態綁定完成。繼續是一種代碼或許功效的傳承同享,從說話的角度它是內在的、情勢上的,極易懂得。而靜態綁定章是從說話的底層完成包管了多態的產生——在運轉期依據基類指針或許援用指向的真實對象類型肯定挪用的虛函數功效!經由過程帶有虛函數的單一繼續我們可以清晰的懂得繼續的概念、對象模子的散布機制和靜態綁定的產生,便可以完整完全地輿解多態的思惟。為了支撐多態,說話完成必需在時光和空間上支付額定的價值(究竟沒有收費的晚飯,更況且編譯器是毫無情感):
1、類完成時增長了virtual table,用來寄存虛函數地址;
2、類對象中增長了指向虛函數表的指針vptr,以供給runtime的鏈接;
3、在類繼續條理的結構函數中反復設定vptr的初值,以等待指針指向對應類的virtual table;
4、在類繼續條理的析構函數中反復復原vptr的初值;
5、多態產生時(base class指針挪用虛函數)須要經由過程vptr和virtual table表挪用對應函數實體,增長了 一層直接性。
第1、2兩點是多態帶來的空間價值,前面三點則是時光效力上的價值。
2、多重繼續和虛擬繼續
多重繼續具有多個base class,有別於單一繼續(供給了一種“天然多態”情勢)。單一繼續中,基類和派生類具有雷同的內存地址,它們之間的轉換非常天然不須要編譯器的參與。但假如基類中沒有虛函數而派生類中有,單一繼續的天然多態被打破。這類情形下,派生類轉換為基類須要編譯器的參與,用以調劑this指針地址。多重繼續的對象模子較單一繼續龐雜,本源在於derived class objects和其第二或後繼的base class objects之間的“非天然”關系 ,這一點可以從上面的對象模子中看到。派生類和基類之間的非天然多態惹起了一個嚴重的成績(在虛擬繼續中也存在):derived class和第二或後繼base class之間的轉換(豈論是對象間的直接轉換或許經過其所支撐的virtual function機制做轉換)須要調劑this指針的地址,以使其指向完全准確的class object 。
虛擬繼續是一種機制,類經由過程虛繼續指出它所願望同享虛基類的狀況,虛基類在派生條理中只要一份實體。比擬多重繼續,虛擬繼續的難點在於既要辨認出雷同的對象部門又要保持基類和派生類之間的多態關系 。平日情形下,完成虛擬繼續時編譯器將對象朋分為一個不變部分和一個同享部分 。不變部分中的數據,不論後繼若何衍化,老是具有固定的offset,所以這一部門數據可以被直接存取。至於同享部分,所表示的就是virtual base class subobject。這一部門的數據,其地位會由於每次的派生操作而有變更,所以它們只能被直接存取 。各家編譯器完成技巧之間的差別在於直接存取辦法分歧。普通的戰略就是先支配好派生類的不變部門,然後樹立同享部門。虛擬繼續base class和derived class之間非天然的多態關系,它們之間互相轉換時須要對this指針地址停止調劑。因為對virtual base class的支撐,虛擬繼續帶來了額定的累贅和模子龐雜性。
3、多重繼續和虛擬繼續對象模子
形成多重繼續和虛擬繼續較通俗單一繼續龐雜、效力低的實質在於 對象模子內存散布的差別, 這一點從第二部門剖析也能夠看到。上面示例比較列出了通俗單一繼續、多重繼續和虛擬繼續的對象模子。須要解釋的是:C++尺度中並沒有強迫劃定base class members和derived class members之間的順序關系,實際上可以自在支配之,但現實上年夜多半編譯器都邑基類成員放在後面,但虛擬繼續除外。上面也是這類戰略,同時把vptr作為類的第一個成員。
基類Base1、Base2和派生類DerivedSingle、DerivedMulti類界說以下:
class Base1 { public: Base1(void); ~Base1(void); virtual Base1* clone()const; protected: float data_Base1; };
class Base2 { public: Base2(void); ~Base2(void); virtual void mumble(); virtual Base2* clone()const; protected: float data_Base2; };
class DerivedSingle: public Base1 { public: DerivedSingle(void); virtual ~DerivedSingle(void); virtual DerivedSingle* clone() const; protectd: float data_DerivedSingle; };
class DerivedMulti :public Base1, public Base2 { public: DerivedMulti(void); virtual ~DerivedMulti(void); virtual DerivedMulti* clone() const; protected: float data_DerivedMulti; };
對象模子以下,虛擬繼續和單一繼續類構造雷同,只是繼續改成了虛擬繼續。
單一繼續:
多重繼續:
虛擬繼續:
為了包管memberwise復制的准確性(不然基類子對象復制給派生類時會產生毛病),C++中包管“基類子對象在派生類中的原樣性 ”。
單一繼續的對象模子出現了一種“天然多態”的情勢,基類和派生類之間的轉換非常天然簡略。但是多重繼續有多個基類,對象有多個vptr指針,關於第二個或後繼基類和派生類之間的轉換須要地址調劑,以指向完全的基類子對象。
虛擬繼續中,為了記住和同享虛擬基類,須要在類中添加指向該基類的指針。從下面的虛擬繼續對象模子中可以看到,固然和單一繼續有雷同的類條理構造,但虛擬繼續打破了單一繼續的“天然多態”情勢,基類和派生類之間的轉換須要調劑this指針的地址。假如是虛擬多重繼續,則虛擬基類/後繼基類和派生類之間的轉換須要this指針地址調劑 。
普通規矩,多重繼續經過指向“第二個或許後繼base class”的指針(援用)來挪用derived class virtual function,該操作所連帶的“需要的this指針調劑”操作,必需在履行期完成,也就是說offset的年夜小、和吧offset加到this指針上頭的那一小段法式代碼,必需有編譯器在某個處所拔出。為了完成this指針調劑引入thunk技巧,所謂thunk是一小段assembly代碼,用來以恰當的offset值調劑this指針,並跳到virtual函數去。Thunk技巧許可virtual table slot持續內含一個簡略的指針,是以多重繼續不須要額定任何空間上的額定累贅。Slots中的地址可以直接指向virtual function,也能夠指向一個相干的thunk(假如須要調劑this指針)。調劑this指針的第二個額定累贅就是,因為兩中分歧的能夠:(1)經過derived class(或許第一個base class)挪用,(2)經過第二個(或許後繼)base class挪用,統一個函數在virtual table中能夠須要多筆對應的slots。而且在第二個或許後繼base class中的虛函數表保留的是thunk代碼地址。
4、 效力
經由過程下面第三部門的剖析,多重繼續和虛擬繼續對象模子的較單一繼續龐雜的對象模子 ,形成了成員拜訪低效力, 表示在兩個方面:對象構建時vptr的屢次設定,和this指針的調劑。關於多種繼續情形的效力比擬以下:
情況 Vptr 設定 Data member 拜訪 virtual Function member 拜訪 效力剖析 單一繼續 no vptr 無 指針/援用/對象拜訪效力雷同 直接拜訪 效力較高 單一繼續 一次 指針/援用/對象拜訪效力雷同 經由過程vptr和vtable拜訪 多態的引入,帶來了設定vptr和直接拜訪虛函數等效力的下降 多重繼續 屢次 指針/援用/對象拜訪效力雷同 經由過程vptr和vtable拜訪,經由過程第二或許後繼base類指針拜訪須要調劑this指針 除單一繼續效力下降的情況,調劑this指針也帶來了效力的下降 虛擬繼續 屢次 對象/指針/運用拜訪效力較低 經由過程vptr和vtable拜訪,拜訪虛基類須要調劑this指針 除單一繼續效力下降的情況,調劑this指針也帶來了效力的下降
多態中的data member拜訪
考核多態中幾種繼續情況的data member成員拜訪效力的症結是:members的offset地位在編譯期能否可以或許肯定。 假如拜訪的成員在編譯期便可以肯定下offset地位,不會帶來額定的累贅。
實際上針對下面的繼續類型,經由過程類對象拜訪,效力完整一樣,由於成員在類中的地位在編譯期是可以肯定的。經由過程援用或許指針拜訪,除一種情況,下面的繼續類型效力也完整雷同 。破例情況是:經由過程指針和援用拜訪虛擬基類的數據成員。由於虛擬基類在分歧的繼續條理中,其offset地位是變更的,而且沒法經由過程指針或許援用類型肯定指針指向對象的真實類型,所以編譯期沒法肯定offset地位,只能在運轉期經由過程類型信息肯定。
現實上詳細繼續(非virtual繼續)其實不會增長空間或許存取時光上的額定累贅,然則虛擬繼續的“直接性”壓制了“把一切運算都移往緩存器履行”的優化才能,即便經由過程類對象拜訪編譯器也會像看待指針一樣(今朝是,編譯器都沒能辨認出對“繼續而來的data member”的存取是經由過程一個非多態對象,因此不須要履行期的直接存取), 效力使人擔憂。但直接性其實不會嚴重影響非優化法式的履行效力,各類型繼續效力差異不年夜。普通來講,virtual base class最有用的應用情勢:一個籠統的virtual base class,沒有任何data members。
多態中的function member拜訪
在C++中,nonmember/static member/nonstatic member函數都被轉化為完整雷同的情勢(經由過程managling定名處置),所以它們的效力完整雷同。
假如是經由過程援用和指針挪用虛函數,效力將會下降,這是由C++多態性質決議的。而多重繼續和虛擬繼續中虛函數的挪用比單一繼續的效力更低。這個從下面表格可以清晰的看出來:this指針調(好比經由過程thunk技巧調劑)和屢次初始化vptr。固然,請記住:經由過程對象拜訪虛函數和拜訪非虛成員函數效力是一樣的。在挪用虛函數而又不須要多態的情形下,可以明白地挪用該函數實體:類名::函數名,壓抑因為虛擬機制而發生的不用要的反復挪用操作。
this指針地址調劑
多重繼續和虛擬繼續中this指針調劑使得這兩種繼續效力下降,現實編程時應當有所小心。上面列出罕見的須要調劑this指針的情況:
1、new 派生類給第二(後繼)個基類指針或經由過程第二(後繼)base class挪用派生類虛析構函數
必需調劑Derived對象的地址,以使其指向Base2 subobject對象。當刪除基類指向的對象時必需再一次調劑,使其指向Derived對象的肇端地址,但是這個調劑只能在履行期完成,在編譯時沒法肯定指針指向的對象類類型。
下次你看到這類情形不要獵奇:pBase2不等於pDerived。
Derived* pDerived = new Derived; Base2* pBase2 = pDerived; // Base2為Derived的第二個基類 pBase2 != pDerived; // 二者不等
2、經由過程派生類指針挪用第二或後繼base class具有的虛函數
假如想准確挪用必需在編譯時調劑派生類指針,以指向後繼base subobject挪用准確的虛函數。由下面的模子圖可以看到:假如經由過程派生類指針挪用mumble函數,而mumble函數只存在於後繼類的虛函數表中,故必需調劑之。
3、後繼base class指針挪用前往derived class type的虛函數而且賦值給另外一後繼base class指針時
示例以下:
Base2* pb1 = new Derived; // 調劑指針指向base2 clss子對象 Base2* pb2 = pb1->clone(); // pb1被調劑至Derived對象的地址,發生新的對象,再次調劑對象指針指向base2基類子對象,賦值給pb2。
記住:Base class指針必定得指向一個完全的與本身類型雷同的對象或許子對象地址,不知足這個前提的情況都須要this指針的調劑。
具體常識請參考:《Inside The C++ Object Model》。