国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深度解讀《深度探索C++對(duì)象模型》之C++虛函數(shù)實(shí)現(xiàn)分析(一)

這篇具有很好參考價(jià)值的文章主要介紹了深度解讀《深度探索C++對(duì)象模型》之C++虛函數(shù)實(shí)現(xiàn)分析(一)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

接下來(lái)我將持續(xù)更新“深度解讀《深度探索C++對(duì)象模型》”系列,敬請(qǐng)期待,歡迎關(guān)注!也可以關(guān)注公眾號(hào):iShare愛分享,自動(dòng)獲得推文和全部的文章列表。

假如有這樣的一段代碼,代碼中定義了一個(gè)Object類,類中有一個(gè)成員函數(shù)print,通過以下的兩種調(diào)用方式調(diào)用:

Object b;
Object* p = new Object;
b.print();
p->print();

請(qǐng)問這兩種方式有什么區(qū)別嗎?在效率上一樣嗎?答案是不確定。因?yàn)榈每闯蓡T函數(shù)print的聲明方式,它可能是靜態(tài)的,可能是非靜態(tài)的,也可能是一個(gè)虛函數(shù)。還得看Object類的具體定義,它可能是獨(dú)立的類,也有可能是經(jīng)過多重繼承來(lái)的類,或者繼承的父類中有一個(gè)虛基類。

靜態(tài)成員函數(shù)和非虛成員函數(shù)比較簡(jiǎn)單,我們?cè)谙乱恍」?jié)簡(jiǎn)單介紹一下即可,本文重點(diǎn)講解虛函數(shù)的實(shí)現(xiàn)及其效率。

成員函數(shù)種類

  • 非靜態(tài)成員函數(shù)

非靜態(tài)成員函數(shù)和普通的非成員函數(shù)是一樣的,它也是被編譯器放置在代碼段中,且可以像普通函數(shù)那樣可以獲取到它的地址。和普通非成員函數(shù)的區(qū)別是它的調(diào)用必須得經(jīng)由一個(gè)對(duì)象或者對(duì)象的指針來(lái)調(diào)用,而且可以直接訪問類中非公開的數(shù)據(jù)成員。下面的代碼打印出函數(shù)的地址:

#include <cstdio>

class Object {
public:
    void print() {
        printf("a=%d, b=%d\n", a, b);
    }
    int a = 1;
    int b = 2;
};

void printObject(Object* obj) {
    printf("a=%d, b=%d\n", obj->a, obj->b);
}

int main() {
    printf("Object::print = %p\n", &Object::print);
    printf("printObject = %p\n", &printObject);

    return 0;
}

程序的輸出結(jié)果如下,從打印結(jié)果來(lái)看,兩者的地址比較相近,說(shuō)明它們都是一起放在代碼段中的,從生成的匯編代碼也可以看出來(lái)。

Object::print = 0x1007b3f30
printObject = 0x1007b3e70

非靜態(tài)成員函數(shù)和普通非成員函數(shù)的運(yùn)行效率上也是一樣的,普通非成員函數(shù)的實(shí)現(xiàn)上,對(duì)類中成員的訪問看起來(lái)像是要經(jīng)過指針的間接訪問,如obj->a,非靜態(tài)成員函數(shù)的訪問看起來(lái)更直接一點(diǎn),直接可以對(duì)類中的成員進(jìn)行存取,好像是非靜態(tài)成員函數(shù)的效率更高一些,其實(shí)不然,非靜態(tài)成員函數(shù)的調(diào)用,編譯器會(huì)隱式的把它轉(zhuǎn)換成另一種形式:

Object obj;
obj.print();
// 轉(zhuǎn)換成:
print(&obj);
// print的定義轉(zhuǎn)換成:
print(Object* const this) {
    printf("a=%d, b=%d\n", this->a, this->b);
}

兩者在本質(zhì)上是一樣的,查看生成的匯編代碼也是一樣的。另外也說(shuō)明了為什么非靜態(tài)成員函數(shù)要經(jīng)由一個(gè)對(duì)象或?qū)ο蟮闹羔榿?lái)調(diào)用。

  • 靜態(tài)成員函數(shù)

上面提到的非靜態(tài)成員函數(shù)的調(diào)用,必須要經(jīng)由類的對(duì)象來(lái)調(diào)用,是因?yàn)樾枰獙?duì)象的地址作為函數(shù)的參數(shù),也就是隱式的this指針,這樣在函數(shù)中訪問類的非靜態(tài)數(shù)據(jù)成員時(shí)將綁定到此地址上,也就是將此地址作為基地址,經(jīng)過偏移得到數(shù)據(jù)成員的地址。但是如果函數(shù)中不需要訪問非靜態(tài)數(shù)據(jù)成員的話,是不需要this指針的,但目前的編譯器并不區(qū)分這種情況。靜態(tài)成員函數(shù)不能訪問類中的非靜態(tài)數(shù)據(jù)成員,所以是不需要this指針的,如Object類中定義了靜態(tài)成員函數(shù)static int static_func(),通過對(duì)象調(diào)用:

Object obj;

obj.static_func();

或者通過對(duì)象的指針調(diào)用:

Object* pobj = new Object;

pobj->static_func();

最終都會(huì)轉(zhuǎn)換成員如下的形式:

Object::static_func();

通過對(duì)象或者對(duì)象的指針來(lái)調(diào)用只是語(yǔ)法上的便利而已,它并不需要對(duì)象的地址作為參數(shù)(this指針)。

那么靜態(tài)成員函數(shù)存在的意義是什么?靜態(tài)成員函數(shù)在C++誕生之初是不支持的,是在后面的版本中增加進(jìn)去的。假設(shè)不支持靜態(tài)成員函數(shù)時(shí),類中有一個(gè)非公開的靜態(tài)數(shù)據(jù)成員,如果外面的代碼需要訪問這個(gè)靜態(tài)數(shù)據(jù),那么就需要寫一個(gè)非靜態(tài)成員函數(shù)來(lái)存取它,而非靜態(tài)成員函數(shù)需要經(jīng)由對(duì)象來(lái)調(diào)用,但有時(shí)候在這個(gè)時(shí)間點(diǎn)沒有創(chuàng)建一個(gè)對(duì)象或者沒有必要?jiǎng)?chuàng)建一個(gè)對(duì)象,那么就有了以下的變通做法:

// 假設(shè)定義了get_static_var函數(shù)用于返回靜態(tài)數(shù)據(jù)成員
((Object*) 0))->get_static_var();
// 編譯器會(huì)轉(zhuǎn)換成:
get_static_var((Object*) 0));

上面的代碼把0強(qiáng)制轉(zhuǎn)換為Object類型的指針,然后經(jīng)由它來(lái)調(diào)用非靜態(tài)成員函數(shù),編譯器會(huì)把0作為對(duì)象的地址傳遞給函數(shù),但函數(shù)中不會(huì)使用這個(gè)0,所以不會(huì)出現(xiàn)問題。由于有這些需求的存在,C++標(biāo)準(zhǔn)委員會(huì)增加了支持靜態(tài)成員函數(shù),靜態(tài)成員函數(shù)可以訪問類中的非公開的靜態(tài)數(shù)據(jù)成員,且不需要經(jīng)由類的對(duì)象來(lái)調(diào)用。

靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù)、普通函數(shù)一樣都是存儲(chǔ)在代碼段中的,也可以獲取到它的地址,它是一個(gè)實(shí)際的內(nèi)存的地址,是一個(gè)數(shù)據(jù),如上面定義的static_func函數(shù),它的類型為int (*)(),就是一個(gè)普通的函數(shù)類型。而非靜態(tài)成員函數(shù),返回的是一個(gè)“指向類成員函數(shù)的指針”,如上面定義的print函數(shù),返回的類型是:

void (Object::*) ();

靜態(tài)成員函數(shù)基本上等同于普通函數(shù),所以和C語(yǔ)言結(jié)合編程時(shí),可以作為回調(diào)函數(shù)傳遞給C語(yǔ)言寫的函數(shù)。

總結(jié)一下,靜態(tài)成員函數(shù)具有以下的特性:

    • 靜態(tài)成員函數(shù)不能存取類中的非靜態(tài)數(shù)據(jù)成員。
    • 靜態(tài)成員函數(shù)不能被聲明為const、volatile或者是virtual。
    • 靜態(tài)成員不需要經(jīng)由類的對(duì)象來(lái)調(diào)用。
  • 虛函數(shù)

虛函數(shù)是否也可以像非虛函數(shù)那樣獲取到它的地址呢?我們寫個(gè)程序來(lái)測(cè)試一下。

#include <cstdio>

class Object {
public:
    virtual void virtual_func1() {
        printf("this is virtual function 1\n");
    }
    virtual void virtual_func2() {
        printf("this is virtual function 2\n");
    }
};

int main() {
    printf("Object::virtual_func1 = %p\n", &Object::virtual_func1);
    printf("Object::virtual_func2 = %p\n", &Object::virtual_func2);
    return 0;
}

上面程序的輸出:

Object::virtual_func1 = 0x0
Object::virtual_func2 = 0x8

程序的輸出結(jié)果并不是一個(gè)內(nèi)存地址,而是一個(gè)數(shù)字,其實(shí)這是一個(gè)偏移值,對(duì)應(yīng)的是這個(gè)虛函數(shù)在虛函數(shù)表中的位置,一個(gè)位置占用8字節(jié)大小,第一個(gè)是0,第二個(gè)是8,以此類推,每多一個(gè)虛函數(shù),就在這個(gè)表中占用一個(gè)位置??雌饋?lái)像是無(wú)法獲取到虛函數(shù)的地址,其實(shí)不然,虛函數(shù)的地址就存放在虛函數(shù)表中,只是我們無(wú)法直接獲取到它,但是我們記得,如果有虛函數(shù)時(shí),對(duì)象的前面會(huì)被編譯器插入一個(gè)虛函數(shù)表指針,這個(gè)指針就是指向類的虛函數(shù)表,我們可以通過它來(lái)獲取到虛函數(shù)的地址,下面演示一下通過非常規(guī)手段來(lái)調(diào)用虛函數(shù)的做法:

#include <cstdio>

class Object {
public:
    virtual void virtual_func1() {
        printf("this is virtual function 1\n");
    }
    virtual void virtual_func2() {
        printf("this is virtual function 2\n");
    }
};

int main() {
    Object* pobj = new Object;
    using Fun = void (*)(void);
    Fun** ptr = (Fun**)pobj;
    printf("vptr = %p\n", *ptr);
    for (auto i = 0; i < 2; ++i) {
        Fun fp = *(*ptr + i);	//取得虛函數(shù)的內(nèi)存地址
        printf("vptr[%d] = %p\n", i, fp);
        fp();	//此行調(diào)用虛函數(shù)
    }
    delete pobj;

    return 0;
}

程序的輸出結(jié)果:

vptr = 0x100264030
vptr[0] = 0x100263ea4
this is virtual function 1
vptr[1] = 0x100263ecc
this is virtual function 2

可以看到,虛函數(shù)的地址不光可以獲取得到,而且還可以直接調(diào)用它,調(diào)用它的前提是函數(shù)中沒有訪問類的非靜態(tài)數(shù)據(jù)成員,不然就會(huì)出現(xiàn)運(yùn)行錯(cuò)誤。vptr就是寫入到對(duì)象前面的虛函數(shù)表指針,它的值就是虛函數(shù)表在內(nèi)存中的地址,虛函數(shù)表中記錄了兩項(xiàng)內(nèi)容,對(duì)應(yīng)了兩個(gè)虛函數(shù)的地址,即vptr[0]是虛函數(shù)virtual_func1的地址,vptr[1]是虛函數(shù)virtual_func2的地址。把他們強(qiáng)制轉(zhuǎn)換成普通函數(shù)的類型指針,然后可以直接調(diào)用他們,所以這里是沒有對(duì)象的this指針的,也就不能訪問類中的非靜態(tài)數(shù)據(jù)成員了。

虛函數(shù)的實(shí)現(xiàn)

從上一小節(jié)中我們已經(jīng)窺探到虛函數(shù)的一般實(shí)現(xiàn)模型,每一個(gè)類有一個(gè)虛函數(shù)表,虛函數(shù)表中包含類中每個(gè)虛函數(shù)的地址,然后每個(gè)對(duì)象的前面會(huì)被編譯器插入一個(gè)指向虛函數(shù)表的指針,同一個(gè)類的所有對(duì)象都共享同一個(gè)虛函數(shù)表。接下來(lái)的內(nèi)容中將詳細(xì)分析虛函數(shù)的實(shí)現(xiàn)細(xì)節(jié),包括單一繼承、多重繼承和虛繼承的情況。

多態(tài)是C++中最重要的特性之一,也是組成面向?qū)ο缶幊谭妒降幕?,虛函?shù)則是為多態(tài)而生。那么何為多態(tài)?多態(tài)是在基類中定義一組接口,根據(jù)不同的業(yè)務(wù)場(chǎng)景派生出不同的子類,在子類中實(shí)現(xiàn)接口,上層代碼根據(jù)業(yè)務(wù)邏輯調(diào)用接口,不關(guān)心接口的具體實(shí)現(xiàn)。在代碼中,一般是聲明一個(gè)基類的指針,此指針在運(yùn)行期間可能指向不同的派生類,然后通過基類的指針調(diào)用一個(gè)接口,這個(gè)接口在不同的派生類中有不同的實(shí)現(xiàn),所以根據(jù)基類的指針指向哪個(gè)具體的派生類,調(diào)用的就是這個(gè)派生類的實(shí)例。假設(shè)有一個(gè)名稱為print的接口,p是基類類型的指針,那么下面的調(diào)用:

p->print();

是如何識(shí)別出要實(shí)施多態(tài)的行為?以及如何調(diào)用到具體哪個(gè)派生類中的print?如果是在指針類型上增加信息,以指明具體所指對(duì)象的類型,那么會(huì)改變指針原有的語(yǔ)義,造成和C語(yǔ)言的不兼容,而且也不是每個(gè)類型都需要這個(gè)信息,這會(huì)造成不必要的空間浪費(fèi)。如果是在每個(gè)類對(duì)象中增加信息,那么在不需要多態(tài)的對(duì)象中也需要存放這些信息,也會(huì)造成空間上的浪費(fèi)。因此增加了一個(gè)關(guān)鍵字virtual,用于修飾那些需要多態(tài)的函數(shù),這樣的函數(shù)就叫做虛函數(shù),所以識(shí)別一個(gè)類是否支持多態(tài),就看這個(gè)類中是否聲明了虛函數(shù)。只要類中有虛函數(shù),就說(shuō)明需要在類對(duì)象中存儲(chǔ)運(yùn)行期的信息。

那么在對(duì)象中要存儲(chǔ)哪些信息才能夠保證保證上面代碼中print的調(diào)用是調(diào)用到正確的派生類中的實(shí)例呢?要調(diào)用到正確的print實(shí)例,我們需要知道:

  • p指向具體的對(duì)象類型,讓我們知道要調(diào)用哪個(gè)print;
  • print的位置,以便我們可以正確調(diào)用它。

要如何實(shí)現(xiàn)它,不同的編譯器可能有不同的實(shí)現(xiàn)方法,通常是使用虛函數(shù)表的做法。編譯器在編譯的過程中,收集到哪些是虛函數(shù),然后將這些虛函數(shù)的地址存放一個(gè)表格中,這些虛函數(shù)的地址在編譯期間確定的,運(yùn)行期間是不會(huì)改變的,虛函數(shù)的個(gè)數(shù)也是固定的,在程序的執(zhí)行期間不能刪除或者增加,所以表格的大小也是固定的,這個(gè)過程由編譯器在編譯期間完成。表格中虛函數(shù)的位置按照類中聲明的順序,位置是固定不變的,我們?cè)谏瞎?jié)中通過虛函數(shù)名稱打印出來(lái)的值就是虛函數(shù)在虛函數(shù)表中的位置,即相對(duì)于表格首地址的偏移值。

有了這個(gè)表格,那么如何尋址到這個(gè)表格呢?方法就是編譯器根據(jù)類中是否有虛函數(shù),如果有虛函數(shù),就在類的構(gòu)造函數(shù)里插入一些匯編代碼,在構(gòu)造對(duì)象時(shí),在對(duì)象的前面插入一個(gè)指針,這個(gè)指針指向這個(gè)虛函數(shù)表,所以這個(gè)指針也叫做虛函數(shù)表指針。下面以具體的代碼來(lái)看看虛函數(shù)是怎么調(diào)用的,把上面的例子main函數(shù)修改如下,其它地方不變:

int main() {
    Object* pobj = new Object;
    pobj->virtual_func1();
    pobj->virtual_func2();
    delete pobj;

    return 0;
}

我們來(lái)看下生成的匯編代碼,首先來(lái)看看虛函數(shù)表長(zhǎng)什么樣:

vtable for Object:
    .quad   0
    .quad   typeinfo for Object
    .quad   Object::virtual_func1()
    .quad   Object::virtual_func2()

它是匯編中定義在數(shù)據(jù)段的一組數(shù)據(jù),“vtable for Object”是它的標(biāo)簽,代表了這個(gè)數(shù)據(jù)區(qū)的起始地址,每一行定義一條數(shù)據(jù),第一列.quad表示數(shù)據(jù)的大小,占用8字節(jié),第二列表示數(shù)據(jù)的值,可以是數(shù)字,也可以是標(biāo)簽,標(biāo)簽是地址的引用。其實(shí)這個(gè)完整的表叫做虛表,它包含了虛函數(shù)表、RTTI信息和虛繼承相關(guān)的信息,Clang和Gcc編譯器是把它們合在一起了,其它編譯器可能是分開的。第一行是虛繼承中用到,之前已經(jīng)講過了,第二行是RTTI信息,這個(gè)以后再講。第三、四行是兩個(gè)虛函數(shù)的地址。

接著看看Object類的默認(rèn)構(gòu)造函數(shù)的代碼:

Object::Object() [base object constructor]: 	# @Object::Object() [base object constructor]
    # 略...
    lea     rcx, [rip + vtable for Object]
    add     rcx, 16
    mov     qword ptr [rax], rcx
    # 略...

之前已經(jīng)講過,有虛函數(shù)時(shí)編譯器會(huì)為類生成默認(rèn)構(gòu)造函數(shù),在默認(rèn)構(gòu)造函數(shù)里在類對(duì)象的前面設(shè)置了虛函數(shù)表指針。在這個(gè)默認(rèn)構(gòu)造函數(shù)里,主要的代碼就是上面這三行,首先獲取虛表(將上面)的起始地址存放在rcx寄存器,然后加上16的偏移值跳過第一、二行,這時(shí)指向第三行數(shù)據(jù),也就是第一個(gè)虛函數(shù)的位置,然后將這個(gè)地址賦值給[rax],rax是存放的對(duì)象的首地址,這就完成了給對(duì)象設(shè)置虛函數(shù)表指針。

接著看main函數(shù)中對(duì)虛函數(shù)的調(diào)用:

main:								# @main
    # 略...
  	# 調(diào)用構(gòu)造函數(shù)
    mov     rdi, rax
    mov     qword ptr [rbp - 32], rdi       # 8-byte Spill
    call    Object::Object() [base object constructor]
    mov     rax, qword ptr [rbp - 32]       # 8-byte Reload
    mov     qword ptr [rbp - 16], rax
		# 調(diào)用第一個(gè)虛函數(shù)
    mov     rdi, qword ptr [rbp - 16]
    mov     rax, qword ptr [rdi]
    call    qword ptr [rax]
  	# 調(diào)用第二個(gè)虛函數(shù)
    mov     rdi, qword ptr [rbp - 16]
    mov     rax, qword ptr [rdi]
    call    qword ptr [rax + 8]
    # 略...

上面匯編代碼中的第4行rax是調(diào)用new函數(shù)后返回來(lái)的地址,也就是pobj指針,把它存放到rdi寄存器中作為參數(shù),同時(shí)也保存到??臻grbp - 32中,然后調(diào)用構(gòu)造函數(shù),構(gòu)造完成之后再拷貝這個(gè)地址到棧空間rbp - 16中。接下來(lái)的第10到12行是第一個(gè)虛函數(shù)的調(diào)用,將對(duì)象的首地址加載到rdi寄存器中,然后對(duì)其取內(nèi)容,也就是是相當(dāng)于指針的解引用,即 (*pobj),取得的內(nèi)容即是構(gòu)造函數(shù)中設(shè)置的虛函數(shù)表的地址,它是一個(gè)指向第一個(gè)虛函數(shù)的地址,然后第12行對(duì)其取內(nèi)容,也即是對(duì)這個(gè)地址解引用,取得第一個(gè)虛函數(shù)的地址,然后以rdi寄存器(即對(duì)象的首地址)為第一個(gè)參數(shù)調(diào)用它,相當(dāng)于:virtual_func1(pobj)。第14到16行是對(duì)第二個(gè)虛函數(shù)的調(diào)用,流程和第一個(gè)基本一樣,區(qū)別在于將虛函數(shù)表的地址加上8的偏移量以指向第二個(gè)虛函數(shù)。

如果在一個(gè)虛函數(shù)中調(diào)用另一個(gè)虛函數(shù)又會(huì)怎樣?第一個(gè)虛函數(shù)已經(jīng)決議出是調(diào)用哪個(gè)對(duì)象的實(shí)例了,那么在其中調(diào)用其它虛函數(shù)還需要再動(dòng)態(tài)決議嗎?把main函數(shù)中對(duì)第二個(gè)虛函數(shù)的調(diào)用去掉,在第一個(gè)虛函數(shù)中增加以下代碼:

virtual_func2();
Object::virtual_func2();

來(lái)看下對(duì)應(yīng)生成的匯編代碼,其它代碼都差不多,主要看virtual_func1函數(shù)的代碼:

Object::virtual_func1():            # @Object::virtual_func1()
    # 略...
    mov     rdi, qword ptr [rbp - 16]       # 8-byte Reload
    mov     rax, qword ptr [rdi]
    call    qword ptr [rax + 8]
    mov     rdi, qword ptr [rbp - 16]       # 8-byte Reload
    call    Object::virtual_func2()
    # 略...

rbp - 16保存的是對(duì)象的首地址,第3到5行對(duì)應(yīng)的是上面C++代碼中第一句的調(diào)用,看起來(lái)在虛函數(shù)中調(diào)用另一個(gè)虛函數(shù),用的還是動(dòng)態(tài)決議的方法,這里編譯器沒有識(shí)別出已經(jīng)決議出具體的對(duì)象了。從匯編代碼的第6、7行看到,通過前面加類名的限定符,是直接調(diào)用到這個(gè)函數(shù),如果你明確調(diào)用的是哪個(gè)函數(shù)的話,可以直接在函數(shù)的前面加上類名,這樣就不需要用多態(tài)的方式去調(diào)用了。

如果不是通過指針類型來(lái)調(diào)用虛函數(shù),而是通過對(duì)象來(lái)調(diào)用,結(jié)果是什么情況?把main函數(shù)改成如下:

int main() {
    Object obj;
    obj.virtual_func1();
    obj.virtual_func2();

    return 0;
}

查看main函數(shù)對(duì)應(yīng)的匯編代碼:

main:                           # @main
    # 略...
    lea     rdi, [rbp - 16]
    call    Object::virtual_func1()
    lea     rdi, [rbp - 16]
    call    Object::virtual_func2()
    # 略...

可以看到通過對(duì)象來(lái)調(diào)用虛函數(shù),是直接調(diào)用到這個(gè)對(duì)象的函數(shù)實(shí)例的,沒有使用多態(tài)的方式,所以通過對(duì)象的方式調(diào)用是沒有多態(tài)的行為的,只有通過類的指針或者引用類型來(lái)調(diào)用虛函數(shù),才會(huì)有多態(tài)的行為。

單一繼承下的虛函數(shù)

假設(shè)有以下的類定義及繼承關(guān)系:

class Point {
public:
    Point(int x = 0) { _x = x; }
    virtual ~Point() = default;
    virtual void drawLine() = 0;
    int x() { return _x; }
    virtual int y() { return 0; }
    virtual int z() { return 0; }
private:
    int _x;
};
class Point2d: public Point {
public:
    Point2d(int x = 0, int y = 0): Point(x) { _y = y; }
    virtual ~Point2d() = default;
    void drawLine() override { }
    virtual void rotate() { }
    int y() override { return _y; }
private:
    int _y;
};
class Point3d: public Point2d {
public:
    Point3d(int x = 0, int y = 0, int z = 0): Point2d(x, y) { _z = z; }
    virtual ~Point3d() = default;
    void drawLine() override { }
    void rotate() override { }
    int z() override { return _z; }
private:
    int _z;
};

int main() {
    Point* p = new Point3d(1, 1, 1);
    printf("z = %d\n", p->z());
    delete p;
    return 0;
}

先來(lái)看看生成的匯編代碼中的虛函數(shù)表:

vtable for Point:
    .quad   0
    .quad   typeinfo for Point
    .quad   Point::~Point() [base object destructor]
    .quad   Point::~Point() [deleting destructor]
    .quad   __cxa_pure_virtual
    .quad   Point::y()
    .quad   Point::z()

vtable for Point2d:
    .quad   0
    .quad   typeinfo for Point2d
    .quad   Point2d::~Point2d() [base object destructor]
    .quad   Point2d::~Point2d() [deleting destructor]
    .quad   Point2d::drawLine()
    .quad   Point2d::y()
    .quad   Point::z()
    .quad   Point2d::rotate()

vtable for Point3d:
    .quad   0
    .quad   typeinfo for Point3d
    .quad   Point3d::~Point3d() [base object destructor]
    .quad   Point3d::~Point3d() [deleting destructor]
    .quad   Point3d::drawLine()
    .quad   Point2d::y()
    .quad   Point3d::z()
    .quad   Point3d::rotate()

每個(gè)類都有一個(gè)對(duì)應(yīng)的虛函數(shù)表,虛函數(shù)表中的內(nèi)容主要來(lái)自于三方面:

  • 改寫基類中對(duì)應(yīng)的虛函數(shù),用自己實(shí)現(xiàn)的虛函數(shù)的地址寫入到對(duì)應(yīng)表格中的位置;
  • 從基類中繼承而來(lái)的虛函數(shù),直接拷貝基類虛函數(shù)的地址添加到虛函數(shù)表中;
  • 新增的虛函數(shù),基類中沒有,子類的虛函數(shù)表會(huì)增加一行容納新條目;

基類和子類使用各自的虛函數(shù)表,互不干擾,即使子類中沒有改寫基類的虛函數(shù),也沒有新增虛函數(shù),編譯器也會(huì)為子類新建一個(gè)虛函數(shù)表,內(nèi)容從基類中拷貝過來(lái),內(nèi)容和基類完全一樣。

虛函數(shù)表中的虛函數(shù)的排列順序是固定的,一般是按照在類中的聲明順序,如C++代碼中的這行代碼:

p->z();

要尋址到正確的z函數(shù)實(shí)例的地址,我們首先需要知道p指針?biāo)赶虻木唧w對(duì)象,然后需要知道z函數(shù)在表格中的位置,如上例中,z函數(shù)在第5個(gè)條目,也就是說(shuō)虛函數(shù)表的起始地址加上32的偏移量就可以尋址到它,這個(gè)位置保持不變,無(wú)論p指針指向哪個(gè)對(duì)象,都能找到正確的z函數(shù)。如果子類中有新增的虛函數(shù),新增的虛函數(shù)聲明的位置插在從基類中繼承來(lái)的虛函數(shù)中間,編譯器會(huì)做調(diào)整,把它安排在后面,在原有的順序上再遞增,如上例中的rotate函數(shù)。

如果您感興趣這方面的內(nèi)容,請(qǐng)?jiān)谖⑿派纤阉鞴娞?hào)iShare愛分享并關(guān)注,以便在內(nèi)容更新時(shí)直接向您推送。
深度解讀《深度探索C++對(duì)象模型》之C++虛函數(shù)實(shí)現(xiàn)分析(一)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-856633.html

到了這里,關(guān)于深度解讀《深度探索C++對(duì)象模型》之C++虛函數(shù)實(shí)現(xiàn)分析(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深度解讀《深度探索C++對(duì)象模型》之?dāng)?shù)據(jù)成員的存取效率分析(一)

    接下來(lái)我將持續(xù)更新“深度解讀《深度探索C++對(duì)象模型》”系列,敬請(qǐng)期待,歡迎關(guān)注!也可以關(guān)注公眾號(hào):iShare愛分享,自動(dòng)獲得推文和全部的文章列表。 在《深度解讀《深度探索C++對(duì)象模型》之C++對(duì)象的內(nèi)存布局》這篇文章中已經(jīng)詳細(xì)分析過C++的對(duì)象在經(jīng)過封裝后,在各

    2024年04月22日
    瀏覽(33)
  • 深度解讀《深度探索C++對(duì)象模型》之返回值優(yōu)化

    接下來(lái)我將持續(xù)更新“深度解讀《深度探索C++對(duì)象模型》”系列,敬請(qǐng)期待,歡迎關(guān)注!也可以關(guān)注公眾號(hào):iShare愛分享,自動(dòng)獲得推文和全部的文章列表。 當(dāng)在函數(shù)的內(nèi)部中返回一個(gè)局部的類對(duì)象時(shí),是怎么返回對(duì)象的值的?請(qǐng)看下面的代碼片段: 對(duì)于上面的代碼,是否

    2024年04月22日
    瀏覽(21)
  • 深入分析C++對(duì)象模型之移動(dòng)構(gòu)造函數(shù)

    接下來(lái)我將持續(xù)更新“深度解讀《深度探索C++對(duì)象模型》”系列,敬請(qǐng)期待,歡迎關(guān)注!也可以關(guān)注公眾號(hào):iShare愛分享,自動(dòng)獲得推文和全部的文章列表。 C++11新標(biāo)準(zhǔn)中最重要的特性之一就是引入了支持對(duì)象移動(dòng)的能力,為了支持移動(dòng)的操作,新標(biāo)準(zhǔn)引入了一種新的引用類

    2024年04月22日
    瀏覽(25)
  • 【C++】繼承 ⑦ ( 繼承中的對(duì)象模型分析 | 繼承中的構(gòu)造函數(shù)和析構(gòu)函數(shù) )

    【C++】繼承 ⑦ ( 繼承中的對(duì)象模型分析 | 繼承中的構(gòu)造函數(shù)和析構(gòu)函數(shù) )

    下面有 3 個(gè)類 , 分別是 A 類 , B 類 , C 類 ; A 類是 基類 ; B 類 公有繼承 A 類 , 并定義了新的 成員變量 y ; C 類 公有繼承 B 類 , 并定義了新的 成員變量 z ; 分別定義上述 3 個(gè)類的對(duì)象 , 上述 3 個(gè)對(duì)象的內(nèi)存模型如下 : A 類對(duì)象 objA 中有一個(gè)成員 int x , 在內(nèi)存中只有一個(gè) int 類型的

    2024年02月08日
    瀏覽(22)
  • 深度探索 Elasticsearch 8.X:function_score 參數(shù)解讀與實(shí)戰(zhàn)案例分析

    深度探索 Elasticsearch 8.X:function_score 參數(shù)解讀與實(shí)戰(zhàn)案例分析

    在 Elasticsearch 中,function_score 可以讓我們?cè)诓樵兊耐瑫r(shí)對(duì)搜索結(jié)果進(jìn)行自定義評(píng)分。 function_score 提供了一系列的參數(shù)和函數(shù)讓我們可以根據(jù)需求靈活地進(jìn)行設(shè)置。 近期有同學(xué)反饋,function_score 的相關(guān)參數(shù)不好理解,本文將深入探討 function_score 的核心參數(shù)和函數(shù)。 Elasticsear

    2024年02月14日
    瀏覽(24)
  • 【C++干貨基地】面向?qū)ο蠛诵母拍钆c實(shí)踐原理:拷貝構(gòu)造函數(shù)的全面解讀

    【C++干貨基地】面向?qū)ο蠛诵母拍钆c實(shí)踐原理:拷貝構(gòu)造函數(shù)的全面解讀

    ?? 鴿芷咕 :個(gè)人主頁(yè) ??? 個(gè)人專欄 : 《C++干貨基地》《粉絲福利》 ??生活的理想,就是為了理想的生活! ??哈嘍各位鐵汁們好啊,我是博主鴿芷咕《C++干貨基地》是由我的襄陽(yáng)家鄉(xiāng)零食基地有感而發(fā),不知道各位的城市有沒有這種實(shí)惠又全面的零食基地呢?C++ 本身作

    2024年03月13日
    瀏覽(28)
  • C++奇跡之旅:探索類對(duì)象模型內(nèi)存的存儲(chǔ)猜想

    C++奇跡之旅:探索類對(duì)象模型內(nèi)存的存儲(chǔ)猜想

    上回我們學(xué)習(xí)了類的定義,初步了解了什么是類?類的定義,以及類的三個(gè)訪問限定符: public , private , protected ,本小節(jié)將講解類的實(shí)例化,類對(duì)象模型的猜想存儲(chǔ),及三種簡(jiǎn)單類的計(jì)算。 在 C++ 中,類的實(shí)例化是指創(chuàng)建一個(gè)類的對(duì)象。當(dāng)我們定義了一個(gè)類之后,就可以根據(jù)

    2024年04月12日
    瀏覽(20)
  • 【C++學(xué)習(xí)】類和對(duì)象 | 拷貝構(gòu)造 | 探索拷貝構(gòu)造函數(shù)為什么需要引用傳參 | 深拷貝 | 初識(shí)運(yùn)算符重載

    【C++學(xué)習(xí)】類和對(duì)象 | 拷貝構(gòu)造 | 探索拷貝構(gòu)造函數(shù)為什么需要引用傳參 | 深拷貝 | 初識(shí)運(yùn)算符重載

    上一篇文章我們開始學(xué)習(xí)類內(nèi)的默認(rèn)成員函數(shù), 這里是傳送門,有興趣可以去看看:http://t.csdn.cn/iXdpH 這篇文章我們繼續(xù)來(lái)學(xué)習(xí)類和對(duì)象的知識(shí)。 目錄 寫在前面: 1. 拷貝構(gòu)造 2. 拷貝構(gòu)造函數(shù)為什么需要引用傳參? 3. 深拷貝 4. 初識(shí)運(yùn)算符重載 寫在最后: 我們?cè)趧?chuàng)建一個(gè)對(duì)

    2024年02月11日
    瀏覽(24)
  • C++類和對(duì)象-C++對(duì)象模型和this指針->成員變量和成員函數(shù)分開存儲(chǔ)、this指針概念、空指針訪問成員函數(shù)、const修飾成員函數(shù)

    C++類和對(duì)象-C++對(duì)象模型和this指針->成員變量和成員函數(shù)分開存儲(chǔ)、this指針概念、空指針訪問成員函數(shù)、const修飾成員函數(shù)

    #includeiostream using namespace std; //成員變量 和 成員函數(shù) 分開儲(chǔ)存的 class Person { public: ?? ?Person() { ?? ??? ?mA = 0; ?? ?} ?? ?//非靜態(tài)成員變量占對(duì)象空間 ?? ?int mA; ?? ?//靜態(tài)成員變量不占對(duì)象空間 ?? ?static int mB; ?? ?//函數(shù)也不占對(duì)象空間,所有函數(shù)共享一個(gè)函數(shù)實(shí)例

    2024年02月20日
    瀏覽(24)
  • AI大模型探索之路-訓(xùn)練篇3:大語(yǔ)言模型全景解讀

    AI大模型探索之路-訓(xùn)練篇3:大語(yǔ)言模型全景解讀

    大規(guī)模語(yǔ)言模型(Large Language Models,LLM),也稱大語(yǔ)言模型或大型語(yǔ)言模型,是一種由包含數(shù)百億以上參數(shù)的深度神經(jīng)網(wǎng)絡(luò)構(gòu)建的語(yǔ)言模型,通常使用自監(jiān)督學(xué)習(xí)方法通過大量無(wú)標(biāo)注文本進(jìn)行訓(xùn)練。 語(yǔ)言模型旨在對(duì)于人類語(yǔ)言的內(nèi)在規(guī)律進(jìn)行建模,從而準(zhǔn)確預(yù)測(cè)詞序列中未來(lái)

    2024年04月26日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包