C++中多態(tài)與多重繼承實現(xiàn)的sli-創(chuàng)新互聯(lián)

這篇文章將為大家詳細講解有關(guān)C++中多態(tài)與多重繼承實現(xiàn)的sli,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

我們提供的服務(wù)有:網(wǎng)站設(shè)計制作、網(wǎng)站設(shè)計、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、朝陽ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的朝陽網(wǎng)站制作公司

C++ 多態(tài)的虛指針實現(xiàn)

首先討論 C++. 多態(tài)也即子類對父類成員函數(shù)進行了重寫 (Override) 后,將一個子類指針賦值給父類,再對這個父類指針調(diào)用成員函數(shù),會調(diào)用子類重寫版本的成員函數(shù)。簡單的例子:

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!\n"); }
};

class Child : public Parent1 {
  public:
  virtual void sayHello() { printf("Hello from child!\n"); }
};

int main() {
  Parent1 *p = new Child();
  p->sayHello();  // get "Hello from child!"
}

首先需要明白,對于底層實現(xiàn)而言,成員函數(shù)就是第一個參數(shù)為對象指針的函數(shù),編譯器自動將對象指針添加到函數(shù)參數(shù)中并命名為 this 指針,除此之外與普通函數(shù)并無本質(zhì)不同。對于非多態(tài)的成員函數(shù)調(diào)用,與非成員函數(shù)調(diào)用過程基本是一致的,根據(jù)參數(shù)列表(參數(shù)列表中包含對象指針類型)和函數(shù)名在編譯時確定實際調(diào)用的函數(shù)。

為了實現(xiàn)多態(tài),不能只根據(jù)對象指針類型推斷函數(shù)簽名,也即例子中,p->sayHello()這一行代碼在執(zhí)行時不能只根據(jù) p 的類型確認調(diào)用的函數(shù)應(yīng)該是Parent::sayHello還是Child:sayHello。在多態(tài)機制下,每個類父類和子類都需要在其數(shù)據(jù)結(jié)構(gòu)中多攜帶一個指針,這個指針指向該類的虛函數(shù)表。

類的虛函數(shù)表也即所有可能發(fā)生重寫的函數(shù)指針表,對象創(chuàng)建時根據(jù)其實際類型決定其虛函數(shù)指針指向的虛函數(shù)列表。如在上文的例子中,Parent1 和 Child 類的虛函數(shù)列表都只有一個函數(shù),分別是Parent1::sayHelloChild::sayHello. 編譯器在編譯時將會把函數(shù)調(diào)用翻譯為「引用虛函數(shù)表中的第 N 個函數(shù)」這樣的指令,比如本例中翻譯為「引用虛函數(shù)表中第一個函數(shù)」。在運行時讀取虛函數(shù)表中真正的函數(shù)指針。運行時 CPU 代價基本是一次指針解引用和一次下表訪問。

Parent1 和 Child 對象都沒有自定義的數(shù)據(jù)結(jié)構(gòu)。運行以下代碼能夠確認 Parent1 和 Child 對象的真實數(shù)據(jù)結(jié)構(gòu)大小都是 8 字節(jié),也即只有虛函數(shù)列表指針。把 Parent1 和 Child1 對象作為 64 位整數(shù)輸出,可以看到 p1, p2 的值相同,p3 與前兩者不同。這個值也即相應(yīng)類的虛函數(shù)表地址。

Parent1* p1 = new Parent1();
Parent1* p2 = new Parent1();
Parent1* p3 = new Child();
printf("sizeof Parent1: %d, sizeof Child: %d\n",
  sizeof(Parent1), sizeof(Child));
printf("val on p1: %lld\n", *(int64_t*)p1);
printf("val on p2: %lld\n", *(int64_t*)p2);
printf("val on p3: %lld\n", *(int64_t*)p3);

C++ 多態(tài)與多重繼承

有一個非常有意思的問題:C++ 發(fā)生多重繼承時,如何支持多態(tài)。剛剛提到,多態(tài)的原理是編譯器將成員函數(shù)調(diào)用編譯為「引用虛函數(shù)表中第 N 個函數(shù)」,虛函數(shù)表在對象數(shù)據(jù)結(jié)構(gòu)中的位置和要調(diào)用虛函數(shù)列表中的第幾個函數(shù)在編譯時都是需要確定的。多重繼承對象如果只有一個虛函數(shù)列表,那不同父類的虛函數(shù)列表中的位置就要發(fā)生沖突。如果有多個虛函數(shù)列表,編譯時就難以確定虛函數(shù)列表指針在數(shù)據(jù)結(jié)構(gòu)中的位置。C++ 采取了非常精妙的做法:將所有父類的數(shù)據(jù)結(jié)構(gòu)(包括虛指針列表)在該對象的數(shù)據(jù)結(jié)構(gòu)上依次排列,該對象的指針正常指向數(shù)據(jù)結(jié)構(gòu)起始位置。當(dāng)指針發(fā)生類型轉(zhuǎn)換時,C++ 編譯器會對指針的值盡可能的進行調(diào)整,使其指向該指針類型應(yīng)該對應(yīng)的位置。指針的值在這個過程中發(fā)生了變化。

比如,Child 類繼承了 Parent1, Parent2 兩個類,則在 Child 指針轉(zhuǎn)換為 Parent1 指針時,不對指針的值進行調(diào)整,因為 Parent1 是 Child 的第一個父類。但將 Child 轉(zhuǎn)換為 Parent2 時,需要將指針指增加 Parent1 數(shù)據(jù)結(jié)構(gòu)長度的值,使指針指向?qū)?yīng) Parent2 數(shù)據(jù)結(jié)構(gòu)開始位置。在本例子中,Parent1 數(shù)據(jù)結(jié)構(gòu)只有虛函數(shù)列表指針,在 64 位機器上長度為 8. 因此,在 Child 指針轉(zhuǎn)換為 Parent2 指針時,其值增加了 8.

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!\n"); }
};

class Parent2 {
  public:
  virtual void sayHi() { printf("Hi from Parent2!\n"); }
};

class Child : public Parent1, public Parent2 {
  public:
  virtual void sayHello() { printf("Hello from child!\n"); }
  virtual void sayHi() { printf("Hi from child!\n"); }
};

int main() {
  Child *p = new Child();
  printf("size of Child: %d", sizeof(Child));
  printf("pointer val as Child*: %lld\n", int64_t(p));
  printf("pointer val as Parent1*: %lld\n", int64_t((Parent1*)p));
  printf("pointer val as Parent2*: %lld\n", int64_t((Parent2*)p));
}

運行這段代碼,會發(fā)現(xiàn) Child 數(shù)據(jù)結(jié)構(gòu)大小增長到 16,也即兩個指針。并且指針的值在后兩次類型轉(zhuǎn)換時是不同的,在 64 位機器上相差 8 個字節(jié),也即 Parent1 的數(shù)據(jù)結(jié)構(gòu)大小。另外如果將 p 轉(zhuǎn)換成 Void 指針再轉(zhuǎn)換為 Parent 指針,此時編譯器就不能正確推斷這個偏移量,此時就會發(fā)生未定義行為。

這個特性其實說明了一個非常有意思的事實:C++ 編譯器在編譯時能夠推斷指針的偏移量,那么編譯器也應(yīng)該可以推斷該指針指向?qū)ο蟮恼鎸嶎愋?。那么,既然可以編譯時推斷對象真實類型,那要虛函數(shù)表又有何用?直接推斷正確的函數(shù)調(diào)用不就可以了嗎?問題在于,如果真的在編譯時推斷多態(tài)函數(shù)調(diào)用,就意味著要為不同類型的對象生成不一樣的二進制代碼。同一行代碼,根據(jù)指針值的不同,產(chǎn)生的函數(shù)調(diào)用不同。這樣一來也意味第三方庫需要提供源代碼,來進行相關(guān)的推斷,類似于模板庫。這都是不可接受的,因此虛函數(shù)列表仍然有必要。借助虛函數(shù)列表,使用指針的代碼能夠生成一致的機器碼。
從另一個角度理解,編譯器在編譯一個完整的 App 時確實能夠推斷所有變量的真實類型,但這需要聯(lián)系過多上下文。編譯一段代碼卻需要這段代碼輸入?yún)?shù)的除類型之外的上下文信息,并根據(jù)上下文信息生成不同的二進制文件,這是不可接受的。

Java 多態(tài)比較

由于 Java 的多態(tài)機制比 C++ 簡單,理論上可以使用 C++ 的機制實現(xiàn) Java 多態(tài)。但 C++ 跟 Java 有一點決定性的不同:C++ 要求父類成員方法必須有 Virtual 關(guān)鍵字修飾時才能被重寫。這就意味著編譯器在編譯父類時就能確認那些函數(shù)可能被重寫,于是可以對不可能重寫的函數(shù)直接在編譯時決定調(diào)用的具體函數(shù),而對可能重寫的函數(shù)使用虛指針表處理。而 Java 的方法默認都是可以重寫的,因此可以認為 Java 方法調(diào)用都需要經(jīng)過查詢虛函數(shù)列表的過程,會比 C++ 不重寫函數(shù)多一點開銷。

Java 不支持多重繼承,但 Java 支持接口 Interface, 接口跟多重繼承有相似之處,不能簡單的使用一個虛函數(shù)表查找。類需要為其實現(xiàn)的每個 Interface 生成一個虛函數(shù)列表,跟 C++ 的情況類似。OpenJDK 文檔指出,在類定義中找到 Interface 的虛函數(shù)列表的辦法是很粗暴的:在類實現(xiàn)的所有 Interface 列表中遍歷查找。文檔中指出,真正的多重繼承是罕見的,通??梢詺w結(jié)為單繼承。對此遍歷過程可能有各種優(yōu)化,筆者沒有深入了解。

思考 Java 和 C++ 的一點不同:C++ 沒有運行時類型,由編譯器在編譯時盡力保證指針指向的位置有對象正確的數(shù)據(jù)結(jié)構(gòu)。將子類指針賦值給父類指針變量時,編譯器盡力對其進行調(diào)整,但如果發(fā)生了 Void 指針賦值等,則編譯器無法保證指針指向的位置有正確的對象數(shù)據(jù)結(jié)構(gòu)。這一步只要語法上沒有錯誤,就不會立即報錯,編譯器也無法確認是否會發(fā)生問題,一定要等到該指針實際進行解引用等發(fā)生異常才會報錯。Java 有運行時類型,在將對象賦值給不同的類型的變量時,會在運行時進行類型檢查,如果沒有正確的類型繼承關(guān)系,會在賦值時報錯。

另外,對比 Java 的 Interface 和 C++ 的多重繼承,會發(fā)現(xiàn) Interface 的運行時時間開銷要比 C++ 多重繼承大得多。但是 C++ 多重繼承需要為每個父類附加一個指針,并且編譯器在編譯時需要完成更多的工作。Java 相對于 C++ 是更加「強類型」的語言。

關(guān)于“C++中多態(tài)與多重繼承實現(xiàn)的sli”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站bm7419.com,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

分享文章:C++中多態(tài)與多重繼承實現(xiàn)的sli-創(chuàng)新互聯(lián)
新聞來源:http://bm7419.com/article12/didcgc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作虛擬主機、移動網(wǎng)站建設(shè)網(wǎng)站設(shè)計、全網(wǎng)營銷推廣、用戶體驗

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)