精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

原來 C++ 虛函數(shù)是這樣實現(xiàn)的!

開發(fā)
本文讓我們拋開枯燥的概念講解,通過一個有趣的故事,一步步揭開 C++ 對象模型的神秘面紗。

"為什么我的程序這么占內(nèi)存?" 

"虛函數(shù)到底是怎么實現(xiàn)的?" 

"多態(tài)背后的原理是什么?" 

如果你也有這些疑問,那么這篇文章正是為你準(zhǔn)備的。讓我們拋開枯燥的概念講解,通過一個有趣的故事,一步步揭開 C++ 對象模型的神秘面紗。

C++ 對象模型

"誒,小王??!" 老張端著冒著熱氣的咖啡杯走進辦公室,眼睛里閃著神秘的光 ?? "今天咱們來聊個有意思的歷史故事~"

小王從鍵盤上抬起頭,一臉困惑:"歷史?這和代碼有什么關(guān)系?" ??

老張神秘地笑了笑:"你猜猜看,咱們天天用的 C++,最開始是怎么實現(xiàn)的?"

"這還用猜嗎?" 小王信心滿滿,"肯定是直接編譯成機器碼??!" ??

"嘿嘿,不對哦~" 老張喝了口咖啡,露出高深莫測的笑容,"C++ 最初其實是個'翻譯官',叫 Cfront,它的工作就是把 C++ 代碼翻譯成 C 代碼!" ??

"不會吧!" 小王瞪大了眼睛 ?? "這不是多此一舉嗎?"

老張搖搖手指:"聰明著呢!Stroustrup 大師當(dāng)年在貝爾實驗室可是深謀遠慮啊。你想啊,C 編譯器都已經(jīng)很成熟了,何必重復(fù)造輪子呢?而且這樣一來,C++ 代碼還能和 C 代碼愉快地玩耍,簡直是一箭雙雕!" ??

"來來來," 老張站起身,走到白板前,"讓我給你變個魔術(shù),看看 C++ 代碼是怎么'變身'的~" ?

老張揮舞著馬克筆,在白板上畫出一段優(yōu)雅的 C++ 代碼:

// 這是一個典型的 C++ 類定義 ???
class Rectangle {
public:
    // 構(gòu)造函數(shù),初始化寬和高 ?
    Rectangle(int w, int h) : width(w), height(h) {}
    
    // 計算面積的成員函數(shù) ??
    int area() { return width * height; }
    
private:
    int width;   // 矩形的寬 ??
    int height;  // 矩形的高 ??
};

"這段 C++ 代碼經(jīng)過編譯器處理后,本質(zhì)上會變成這樣的 C 代碼" 老張微笑著說

// C 語言結(jié)構(gòu)體定義 ??
struct Rectangle {
    int width;   // 存儲寬度
    int height;  // 存儲高度
};

// 構(gòu)造函數(shù)被轉(zhuǎn)換為普通的 C 函數(shù) ???
void Rectangle_Rectangle(
    struct Rectangle* this, 
    int w, 
    int h
) {
    this->width = w;
    this->height = h;
}

// 成員函數(shù)也變成普通的 C 函數(shù) ??
int Rectangle_area(struct Rectangle* this) {
    returnthis->width * this->height;
}

小王恍然大悟:"原來如此!C++ 的類其實就是在 C 的基礎(chǔ)上做了語法糖啊!"

老張點點頭:"是的!所有的成員函數(shù)都會被轉(zhuǎn)換成普通的 C 函數(shù),只是多了一個 this 指針參數(shù)。這就是為什么我們說,要真正理解 C++,必須先理解 C 語言的基礎(chǔ)。" 

"那繼承是怎么實現(xiàn)的呢?" 小王充滿好奇地問道 ??

老張笑著說 ??:"這個問題問得好!讓我們繼續(xù)深入了解 C++ 對象模型的奧秘..."

簡單對象模型

"來,小王," 老張放下熱騰騰的咖啡 ??,拿起馬克筆 ??,"讓我們來了解一下 C++ 最初的對象模型是什么樣的。這個模型雖然簡單,但對理解繼承特別有幫助哦!" ??

小王立刻坐直了身體,眼睛閃閃發(fā)亮?

"知道為什么現(xiàn)在的 C++ 類寫起來這么優(yōu)雅嗎?" 老張神秘地笑著說 ??♂?,"這還得從上古時代說起..."

老張在白板上寫下一段代碼 ????:

// 一個簡單的學(xué)生類 ????
class Student {
    std::string name;     // 存儲學(xué)生姓名 ??
    int score;           // 記錄考試分數(shù) ??
    void study();       // 學(xué)習(xí)方法 ??
    void doHomework();  // 寫作業(yè)功能 ??
};

"猜猜看,在 C++ 剛誕生的年代,Stroustrup 大師是怎么實現(xiàn)這個類的?" 老張眨眨眼 ??

小王搖搖頭 ??:"難道...和現(xiàn)在不一樣嗎?"

"哈哈,那時候為了簡化編譯器的實現(xiàn),他們設(shè)計了'簡單對象模型'" ??

老張畫出了內(nèi)部實現(xiàn)示意圖 ??:

// 在內(nèi)存中的實際表示
struct Student_Internal {
    void* name_ptr;      // 指向?qū)嶋H的 string 數(shù)據(jù)
    void* score_ptr;     // 指向?qū)嶋H的 int 數(shù)據(jù)
    void* study_ptr;     // 指向 study 函數(shù)
    void* homework_ptr;  // 指向 homework 函數(shù)
};

"看到?jīng)]?" 老張指著圖說 ??,"所有成員,不管是數(shù)據(jù)還是函數(shù),統(tǒng)統(tǒng)變成指針!就像一個巨大的導(dǎo)航表 ??? ??"

小王瞪大了眼睛 ??:"等等...那豈不是一個簡單的 int 也要用指針來存?" ??

"沒錯!" 老張笑著說 ??,"想象一下,你點外賣 ??,每個菜都要先送到隔壁小區(qū),然后給你一張地址條 ??,告訴你:'你的紅燒肉在A棟3層 ??,炒青菜在B棟5層...' ?? ??♂?"

"這...這不是很浪費嗎?" 小王忍不住笑了 ??

"所以?。? 老張喝了口咖啡 ??,"假設(shè)我們創(chuàng)建一個學(xué)生對象 ????:"

// 創(chuàng)建一個學(xué)生對象
Student student;  

// 在內(nèi)存中占用 32 字節(jié)
// 因為有 4 個指針,每個指針 8 字節(jié)
// 4 × 8 = 32 字節(jié) ??

"原本一個 int 只需要 4 字節(jié) ??,現(xiàn)在卻要用 8 字節(jié)的指針去指向它。就像點個炒青菜 ??,還得配個專職導(dǎo)游 ????!" 老張搖頭晃腦地說 ??

"而且啊," 老張拿起筆又在白板上寫道 ??,"你想想訪問成員時會有多麻煩 ??:"

// 簡單對象模型下訪問成員的復(fù)雜過程 ??
void useStudent(Student* s) {
    // 第一步:定位 score 指針的地址 ??
    void** score_ptr_addr = (void**)((char*)s + sizeof(void*));
    
    // 第二步:通過指針找到實際分數(shù) ??
    int* real_score = (int*)(*score_ptr_addr);
    
    // 第三步:終于可以修改分數(shù)了 ??
    *real_score = 100;

    // 調(diào)用方法更復(fù)雜 ??
    // 第一步:找到函數(shù)指針的地址 ??
    void** study_ptr_addr = (void**)((char*)s + 2 * sizeof(void*));
    
    // 第二步:獲取實際的函數(shù)指針 ??
    void (*study_func)(Student*) = 
        (void (*)(Student*))*study_ptr_addr;
    
    // 第三步:調(diào)用函數(shù) ??
    study_func(s);
}

"天哪!" 小王驚呼 ??,"就改個分數(shù)要這么多步驟?"

"是?。? 老張點點頭 ??,"現(xiàn)在我們寫student.score = 100 或student.study() 這么簡單 ?,但在簡單對象模型下,編譯器要做的工作可復(fù)雜了 ??。每次訪問成員都要進行兩次內(nèi)存尋址:一次找到指針 ??,一次通過指針找到實際數(shù)據(jù) ??。"

"而且這還不是全部問題 ??," 老張繼續(xù)說,"想象一下,如果要實現(xiàn)虛函數(shù),還得再加一層間接尋址。性能損失就更大了 ??。"

"那后來呢?" 小王來了興趣 ??

"后來當(dāng)然是改進啦!這就像餐廳最后想通了 ??:'與其把菜放在各個地方,還不如直接送到客人桌上呢!'" 老張眨眨眼 ??,"這就是我們現(xiàn)在用的內(nèi)存模型,數(shù)據(jù)直接存在對象里,該多大就多大。"

"不過呢," 老張神秘地補充道 ??,"這個看似笨拙的想法,后來卻啟發(fā)了'成員指針'的設(shè)計。這又是另一個有趣的故事了..." ?

小王托著下巴 ??:"老張,您這故事講得,把我都聽餓了..." ?? ??

"哈哈,那正好!" 老張站起身 ??,"我請你吃飯 ??,路上再給你講講成員指針的故事!" ???

表格驅(qū)動對象模型

"吃飽了吧?" 老張笑瞇瞇地問道 ??,"現(xiàn)在讓我們繼續(xù)講講 C++ 對象模型的演進。" ??

小王滿足地點點頭 ??:"剛才您說到簡單對象模型的缺點,后來是怎么改進的呢?" ??

"在簡單對象模型之后,出現(xiàn)了一個叫'表格驅(qū)動對象模型'的設(shè)計。" 老張興致勃勃地在白板上畫起了新的示意圖 ??:

// 學(xué)生類的原始定義 ????
class Student {
    std::string name;    // 學(xué)生姓名
    int score;          // 學(xué)生分數(shù)
    void study();      // 學(xué)習(xí)方法
    void doHomework(); // 做作業(yè)方法
};

// ===== 表格驅(qū)動模型的內(nèi)部實現(xiàn) =====

// 數(shù)據(jù)部分:專門存儲成員變量 ??
struct Student_Data {
    std::string name;  // 直接存儲姓名
    int score;        // 直接存儲分數(shù)
};

// 表格部分:管理數(shù)據(jù)和函數(shù) ??
struct Student_Table {
    // 指向?qū)嶋H數(shù)據(jù)的指針
    Student_Data* data;     
    
    // 指向函數(shù)表的指針(存儲所有成員函數(shù))
    void** function_table;   
};

"看出區(qū)別了嗎?" 老張指著圖說 ??,"這個模型把數(shù)據(jù)和函數(shù)分開存儲。數(shù)據(jù)直接存在對象里,函數(shù)則通過一個表來管理。" ???

"這樣做有什么好處呢?" 小王充滿好奇地問道 ??

"首先,數(shù)據(jù)訪問變快了!" 老張興奮地解釋道 ??,"因為數(shù)據(jù)直接存儲,不用再通過指針間接訪問。其次,這種設(shè)計為后來的虛函數(shù)表鋪平了道路。" ?

"啊,原來虛函數(shù)表是從這里來的!" 小王恍然大悟 ??

"沒錯," 老張欣慰地點頭 ??,"這就是為什么我們說要理解 C++ 的歷史演進。每個設(shè)計都不是憑空出現(xiàn)的,都是在解決實際問題中逐步優(yōu)化的結(jié)果。" ??

"不過啊," 老張喝了口咖啡繼續(xù)說 ??,"表格驅(qū)動模型雖然比簡單對象模型好,但還是存在一些問題。" ??

小王認真地聽著 ??

"讓我給你列舉幾個主要問題:" 老張掰著手指數(shù)道 ??

// 問題演示 1: 內(nèi)存占用問題 ??
struct Student_Table {
    Student_Data* data;      // 8字節(jié)
    void** function_table;   // 8字節(jié)
};  // 總共需要 16 字節(jié)!

// 問題演示 2: 訪問效率問題 ??
void example() {
    Student s;
    
    // 訪問數(shù)據(jù)要兩次解引用 
    int score = s.table->data->score;  
    
    // 調(diào)用函數(shù)更復(fù)雜
    void (*func)() = s.table->function_table[0];
    func();
}

"你看," 老張指著代碼說 ??,"首先是內(nèi)存問題。即使一個空類,也要占用至少 16 個字節(jié)來存儲兩個指針!" ??

"其次是性能問題," 老張繼續(xù)解釋 ??,"每次訪問數(shù)據(jù)都要經(jīng)過兩次指針跳轉(zhuǎn),調(diào)用函數(shù)更是要先找表,再找函數(shù)..."

"這不就和簡單對象模型一樣慢嗎?" 小王皺眉道 ??

"沒錯!" 老張點點頭 ??,"所以后來就有了現(xiàn)代 C++ 對象模型,它采用了一種更聰明的方式。" ?

"那現(xiàn)代的對象模型又是什么樣的呢?" 小王繼續(xù)追問 ??

"這個嘛..." 老張神秘地笑了笑 ??,"我們下次再講。" ?? ?

現(xiàn)代 C++ 對象模型

"說到現(xiàn)代 C++ 對象模型," 老張站起來走到白板前,"這可是 Stroustrup 大師經(jīng)過反復(fù)權(quán)衡后的杰作。"

"它有什么特別之處呢?" 小王問道。

"我們用一個經(jīng)典的例子來說明。" 老張在白板上寫道:

// 現(xiàn)代 C++ 對象模型示例 ??
class ModernStudent {
    // 數(shù)據(jù)直接存儲在對象中 ??
    std::string name;   
    int score;         
    
    // 普通函數(shù)直接編譯為獨立函數(shù) ??
    void study() { 
        // 直接訪問數(shù)據(jù)成員
        score += 10;  
    }
    
    // 虛函數(shù)才使用虛表 ??
    virtual void doHomework() {
        // 通過虛表調(diào)用
    }
    
private:
    // 只有需要多態(tài)的類才有虛表指針
    void* vptr;  // 虛函數(shù)表指針 ??
};

"現(xiàn)代對象模型的特點是:" 老張總結(jié)道 ??

  • 數(shù)據(jù)成員直接存儲 ??
  • 普通成員函數(shù)獨立存儲 ??
  • 只在需要多態(tài)時才使用虛表 ??
  • 最大限度減少間接訪問 ??

"這樣既保證了性能,又支持了 C++ 的所有特性!" 老張笑著說 ??

"原來是這樣!" 小王恍然大悟 ??,"那這就解釋了為什么有些類比其他類占用內(nèi)存更多 - 因為它們需要虛表指針!"

"聰明!" 老張贊許地點點頭 ??,"這就是為什么我們說 - 要理解 C++ 的性能特性,必須先理解它的對象模型。" ??

"那虛表具體是怎么工作的呢?" 小王繼續(xù)追問 ??

"這個問題問得好!" 老張眼睛一亮 ?,"不過這是另一個精彩的話題了,我們下次再聊..."

小王若有所思地點點頭 ??,"感覺 C++ 每個特性背后都有這么多故事啊!"

"是啊," 老張笑道 ??,"這就是 C++ 的魅力所在。它的每個設(shè)計決策,都是在實踐中不斷優(yōu)化的結(jié)果。" ??

虛函數(shù)表工作原理

"說到虛函數(shù)表啊..." 老張放下咖啡杯,眼睛里閃著光 ?,"這可是 C++ 里最精妙的設(shè)計之一。"

"為什么這么說?" 小王好奇地問道 ??

"想啊," 老張拿起馬克筆走向白板,"C++ 需要在運行時才能確定調(diào)用哪個函數(shù),但又要保證性能不受太大影響。這就像餐廳里的點菜系統(tǒng),既要讓客人能隨時換菜,又不能讓服務(wù)員跑來跑去問廚師 - 這可怎么辦呢?" ??♂?

"?。【拖癫蛷d的電子菜單?" 小王眼前一亮 ??

"沒錯! ??" 老張開心地笑著說 ??, "虛函數(shù)表就像是每個類的'專屬菜單' ??。來,讓我給你畫個生動的例子..." ?

// 動物基類 ??
class Animal {
public:
    // 構(gòu)造函數(shù),初始化動物名字 ???
    Animal(conststd::string& name) : _name(name) {}
    
    // 虛析構(gòu)函數(shù),確保正確釋放內(nèi)存 ???
    virtual ~Animal() = default;
    
    // 純虛函數(shù),所有動物都要實現(xiàn)發(fā)聲 ??
    virtual void makeSound() = 0;  
    
protected:
    std::string _name;    // 動物的名字 ??
    
private:
    staticint population;  // 所有動物的數(shù)量統(tǒng)計 ??
};

// 貓咪類 - 繼承自動物 ??
class Cat :public Animal {
public:
    // 構(gòu)造小貓咪 ??
    Cat(conststd::string& name) : Animal(name) {}
    
    // 實現(xiàn)貓咪的叫聲 ??
    virtual void makeSound() override { 
        std::cout << "喵喵喵~" << std::endl; 
    }
    
private:
    int _lives = 9;  // 貓有9條命 ?
};

"在現(xiàn)代 C++ 中 ??," 老張拿起馬克筆繼續(xù)畫圖 ??, "這些類在內(nèi)存中的布局是這樣的:"

// Animal 在內(nèi)存中的實際布局 ??
struct Animal_Layout {
    void* vptr;         // 虛函數(shù)表指針 ??
    std::string _name;  // 名字成員變量 ??
};

// Cat 在內(nèi)存中的實際布局 ??
struct Cat_Layout {
    Animal_Layout base;  // 繼承的基類部分 ??
    int _lives;         // Cat獨有的成員 ??
};

// 類外部的靜態(tài)成員 ??
static int Animal::population;  // 存儲在數(shù)據(jù)段中

"這個設(shè)計展示了幾個超級重要的特點 ??:" 老張指著白板興奮地說 ??:

  • 虛表指針總是在最前面 ??
  • 基類成員排在前面 ??
  • 派生類成員在后面 ??
  • 虛函數(shù)通過表格查找調(diào)用 ??

"這樣的設(shè)計既高效又靈活 ??,是 C++ 智慧的結(jié)晶呢!" 老張總結(jié)道 ?

如何訪問虛函數(shù)表? 

"讓我們一步步看看虛函數(shù)調(diào)用背后發(fā)生了什么," 老張拿起馬克筆 ??, "首先創(chuàng)建一個貓咪對象:"

Cat kitty("咪咪");  // 創(chuàng)建貓咪對象

"當(dāng)我們創(chuàng)建這個對象時," 老張解釋道 ????, "編譯器會自動初始化虛表指針(vptr),指向 Cat 類的虛函數(shù)表。"

"接下來,當(dāng)我們調(diào)用虛函數(shù)時:"

kitty.makeSound();  // 看起來很簡單的一行代碼

"但在底層,編譯器會生成一系列復(fù)雜的操作。首先是獲取虛表指針:"

// 第一步:獲取對象的虛表指針
void** vptr = *(void***)(&kitty);  // 從對象內(nèi)存布局的開始位置讀取虛表指針

// 指針層次分析:
// === 第1步:獲取對象地址 ===
Cat* cat_ptr = &kitty;
// cat_ptr 現(xiàn)在指向?qū)ο蟮钠鹗嘉恢?// 因為虛表指針總是在對象的最開始位置,所以這個地址
// 實際上就指向了虛表指針的存儲位置

// === 第2步:轉(zhuǎn)換為 void*** 類型 ===
void*** triple_ptr = (void***)cat_ptr;
// 為什么要轉(zhuǎn)換成 void***?
// - 因為我們要通過這個指針去讀取虛表指針
// - 虛表指針本身的類型是 void**
// - 所以指向虛表指針的指針就是 void***

// === 第3步:解引用獲取虛表指針 ===
void** vptr = *triple_ptr;
// 現(xiàn)在 vptr 就是真正的虛表指針了
// - 它指向了函數(shù)指針數(shù)組(虛函數(shù)表)
// - 類型是 void**,因為它指向的是函數(shù)指針數(shù)組

// 內(nèi)存布局示意:
/*
內(nèi)存地址     內(nèi)容                  類型
0x1000   [ 虛表指針 ]            void**    <-- cat_ptr/triple_ptr 指向這里
         [ name成員 ]            string
         [ lives成員 ]           int

0x2000   [ 析構(gòu)函數(shù)指針 ]        void*     <-- vptr 指向這里(虛函數(shù)表的開始)
         [ makeSound指針 ]       void*
         [ eat指針 ]            void*
         [ purr指針 ]           void*
*/

"讓我們詳細解釋一下這個指針轉(zhuǎn)換過程:" 老張拿起馬克筆畫起示意圖 ??


// 假設(shè)有一個 Cat 對象
Cat kitty("咪咪");

// 獲取虛表指針的詳細步驟分解
void** vptr = *(void***)(&kitty);

/* 讓我們一步步解析這行代碼:

1. &kitty 得到 Cat* 類型
   - 這是對象的起始地址
   - 因為虛表指針在對象的開頭,所以這個地址就是虛表指針的位置

2. (void***) 轉(zhuǎn)換
   - 為什么需要 void***?
   - 因為我們要:
     a) 首先通過指針訪問對象 (第一個*)
     b) 對象開頭存儲的是虛表指針 (第二個*)
     c) 虛表本身是函數(shù)指針數(shù)組 (第三個*)
   - 這就像一個三層的包裝盒:
     最外層: 對象地址
     中間層: 虛表指針
     最內(nèi)層: 函數(shù)指針數(shù)組

3. *(void***) 解引用
   - 這一步實際獲取了虛表指針
   - 結(jié)果類型是 void**,正好是函數(shù)指針數(shù)組的類型
*/

"這樣理解起來容易多了吧?" 老張問道 ??

"哦~" 小王恍然大悟 ??,"原來這些指針操作是為了層層剝開對象的結(jié)構(gòu),最終找到虛函數(shù)表!"

"這里用了兩次指針轉(zhuǎn)換," 老張指著代碼說 ??, "&kitty 得到對象地址,然后通過指針轉(zhuǎn)換和解引用,找到虛表指針。"

"讓我用一個三維數(shù)組的類比來幫大家更好地理解這個指針結(jié)構(gòu)," 老張補充道 ????

// 想象一個三維數(shù)組結(jié)構(gòu)
Class[N][M][K] objects;

/*
三個維度分別代表:
第一維 [N]: 不同的類
    - 每個類都有自己的虛函數(shù)表
    - 比如 Animal、Cat、Dog 等類

第二維 [M]: 虛函數(shù)表
    - 存儲了該類所有的虛函數(shù)指針
    - 包括繼承的、覆寫的和新增的函數(shù)

第三維 [K]: 具體的函數(shù)指針
    - 指向?qū)嶋H的函數(shù)實現(xiàn)
    - 比如 makeSound、eat 等方法

訪問過程就像在這個三維空間中導(dǎo)航:
1. 通過對象找到對應(yīng)的類 (第一維)
2. 獲取該類的虛函數(shù)表 (第二維)
3. 在表中找到具體的函數(shù)指針 (第三維)
*/

// 用代碼表示這個訪問過程
void callVirtualFunction(Cat* obj, int funcIndex) {
    // 第一維:通過對象找到類的虛表指針
    void*** classPtr = (void***)obj;
    
    // 第二維:獲取虛函數(shù)表
    void** vtable = *classPtr;
    
    // 第三維:獲取具體函數(shù)指針
    void* funcPtr = vtable[funcIndex];
    
    // 調(diào)用函數(shù)
    ((void(*)(Cat*))funcPtr)(obj);
}

"看到了嗎?" 老張指著圖說 ?? "就像在一個三維空間中導(dǎo)航:"

  • "第一維就像一個類的博物館 ???,每個展廳都是一個不同的類"
  • "第二維就像每個展廳里的展示柜 ??,里面陳列著該類的所有虛函數(shù)"
  • "第三維就是展示柜中的具體展品 ??,也就是實際的函數(shù)實現(xiàn)"

"當(dāng)我們通過對象調(diào)用虛函數(shù)時,就像是在這個三維空間中找到正確的'展品'。" 老張解釋道 ???

"??!這么一說就清楚多了!" 小王眼睛一亮 ?? "每次解引用就是在不同維度間穿梭!"

如何通過虛函數(shù)表獲取函數(shù)地址?

"接著是在虛表中查找函數(shù)地址:"

// 第二步:在虛表中查找函數(shù)地址
void (*makeSound)(Cat*) = vptr[1];  // 虛表中查找 makeSound 函數(shù)指針

"虛表就像一個函數(shù)指針數(shù)組," 老張繼續(xù)解釋 ??, "每個虛函數(shù)在表中都有固定的位置。這里的 [1] 表示 makeSound 在虛表中的偏移位置。"

"等等," 小王突然舉手問道 ??, "這個 [1] 是怎么來的?為什么是 1 而不是其他數(shù)字?"

"??!問得好!" 老張笑著說 ??, "虛表中的函數(shù)位置是在編譯時就確定好的。讓我詳細解釋一下..."

老張在白板上畫起了新的示意圖:

// Animal 類的虛函數(shù)表布局 ??
struct Animal_VTable {
    // [0] 析構(gòu)函數(shù)永遠在第一個位置
    void (*destructor)(Animal*);     
    // [1] makeSound 在第二個位置
    void (*makeSound)(Animal*);      
    // 如果還有其他虛函數(shù),繼續(xù)往后排...
};

// Cat 類繼承并覆寫了這些函數(shù)
struct Cat_VTable {
    // [0] Cat 的析構(gòu)函數(shù)
    void (*destructor)(Cat*);        
    // [1] Cat 的 makeSound 實現(xiàn)
    void (*makeSound)(Cat*);         
};

"編譯器遵循以下規(guī)則來安排虛函數(shù)的位置 ??:" 老張解釋道:

  • "虛析構(gòu)函數(shù)總是位于索引 [0] 的位置 ??"
  • "其他虛函數(shù)按照它們在基類中首次聲明的順序排列 ??"
  • "派生類如果覆寫了基類的虛函數(shù),就使用相同的位置 ??"
  • "派生類新增的虛函數(shù)放在表的末尾 ??"

"比如說,如果我們擴展一下這個例子:" 老張繼續(xù)寫道:

class Animal {
public:
    virtual ~Animal();                // 位置 [0]
    virtual void makeSound() = 0;     // 位置 [1]
    virtual void eat();               // 位置 [2]
};

class Cat : public Animal {
public:
    virtual ~Cat();                   // 位置 [0]
    virtual void makeSound() override;// 位置 [1]
    virtual void eat() override;      // 位置 [2]
    virtual void purr();              // 位置 [3] - Cat特有
};

"所以當(dāng)我們調(diào)用kitty.makeSound() 時,編譯器知道 makeSound 在位置 [1],這是在編譯時就確定好的,運行時直接用這個固定位置去查找,非常高效!" 老張總結(jié)道 ??

"原來如此!" 小王恍然大悟 ??, "這就像圖書館的分類系統(tǒng),每本書都有固定的位置編號!"

"沒錯!" 老張點頭贊許 ??, "而且這種設(shè)計還保證了即使基類添加了新的虛函數(shù),已有的函數(shù)位置也不會改變,這對二進制兼容性非常重要。"

"最后,才是實際調(diào)用函數(shù):"

// 第三步:調(diào)用找到的函數(shù)
makeSound(&kitty);  // 傳入 this 指針調(diào)用函數(shù)

"看到了嗎?" 老張總結(jié)道 ??, "一個簡單的虛函數(shù)調(diào)用,背后其實包含了三個關(guān)鍵步驟:

  • 獲取虛表指針
  • 查找函數(shù)地址
  • 調(diào)用目標(biāo)函數(shù)

這就是為什么虛函數(shù)調(diào)用會比普通函數(shù)調(diào)用慢一點 - 它需要額外的間接尋址操作。"

小王恍然大悟 ??: "原來如此!這就解釋了為什么有些性能敏感的代碼會避免使用虛函數(shù)。"

"沒錯!" 老張點點頭 ??, "不過現(xiàn)代 CPU 的分支預(yù)測已經(jīng)很強大了,所以除非在特別關(guān)鍵的性能熱點,否則虛函數(shù)的開銷通常不會造成明顯影響。"

"這比之前的簡單對象模型和表格驅(qū)動模型高明多了!" 小王驚嘆道。

"是的," 老張點頭,"這個設(shè)計既保證了性能,又支持了多態(tài)。"

同類對象的虛函數(shù)表共享機制

"對了老張!" 小王突然想到一個問題 ??,"每個對象都有一個虛表指針,那虛函數(shù)表本身是每個對象都有一份嗎?"

老張笑著搖搖頭 ??:"這個問題問得好!虛函數(shù)表是由編譯器為每個類創(chuàng)建的,而不是每個對象。所有同類型的對象共享同一個虛函數(shù)表!"

"讓我畫個圖解釋一下:" 老張走向白板 ??:

// 內(nèi)存布局示意圖 ??

// 代碼段(只讀)中存儲的虛函數(shù)表
const Cat_VTable {              // ?? 所有 Cat 對象共享這個表
    &Cat::destructor,          // [0]
    &Cat::makeSound,          // [1]
    &Cat::eat,               // [2]
    &Cat::purr              // [3]
};

// 堆/棧中的對象
Cat cat1("咪咪");   // ?? 對象1
// {
//     vptr -> Cat_VTable    // 指向共享的虛函數(shù)表
//     name: "咪咪"
//     lives: 9
// }

Cat cat2("花花");   // ?? 對象2
// {
//     vptr -> Cat_VTable    // 指向相同的虛函數(shù)表
//     name: "花花"
//     lives: 9
// }

"你看," 老張指著圖解釋道 ??,"虛函數(shù)表存儲在程序的只讀數(shù)據(jù)段(.rodata)中,是只讀的。每個 Cat 對象的 vptr 都指向這同一個表。這樣設(shè)計有幾個重要好處:"

  • "節(jié)省內(nèi)存 ?? - 不需要為每個對象都存儲一份完整的函數(shù)表"
  • "提高緩存效率 ?? - 因為所有對象共享同一份表,增加了緩存命中率"
  • "保證一致性 ? - 所有對象調(diào)用的都是同一份虛函數(shù)實現(xiàn)"

"哇!這設(shè)計真是太巧妙了!" 小王贊嘆道 ??,"那是不是說,一個程序里面,每個類只會有一份虛函數(shù)表?"

"基本上是這樣。" 老張點點頭 ??,"不過要注意,如果你的程序使用了動態(tài)庫,同一個類可能在不同的動態(tài)庫中各有一份虛函數(shù)表。但在同一個編譯單元內(nèi),確實是共享同一份表的。"

"這就像一個大餐廳的菜單系統(tǒng)," 老張舉例說 ??,"每個服務(wù)員(對象)手里都有一個平板(vptr),但他們都連接到同一個中央點餐系統(tǒng)(虛函數(shù)表)。這樣不管哪個服務(wù)員接單,都能保證點到一樣的菜!" ???

小王若有所思地點點頭 ??:"所以虛函數(shù)的內(nèi)存開銷主要是每個對象都要多存一個指針,而不是虛函數(shù)表本身?"

"聰明!" 老張贊許地說 ??,"這就是為什么在 C++ 中,一個帶有虛函數(shù)的類的對象,至少要比沒有虛函數(shù)的類多占用一個指針的大小。在 64 位系統(tǒng)上就是 8 字節(jié)。"

派生類和基類的虛函數(shù)表關(guān)系

"等等," 小王突然想到什么 ??, "那派生類和基類的虛函數(shù)表是怎么回事?它們也是共享一個表嗎?"

"啊,這個問題問得好!" 老張眼睛一亮 ?, "派生類會有自己獨立的虛函數(shù)表,而不是和基類共享。讓我畫個圖解釋一下:"

// 基類 Animal 的虛函數(shù)表
const Animal_VTable {           // ?? 基類表
    &Animal::destructor,       // [0]
    &Animal::makeSound,       // [1]
    &Animal::eat             // [2]
};

// 派生類 Cat 的虛函數(shù)表
const Cat_VTable {             // ?? 派生類有自己的表
    &Cat::destructor,         // [0] 覆寫的析構(gòu)函數(shù)
    &Cat::makeSound,         // [1] 覆寫的 makeSound
    &Cat::eat,              // [2] 覆寫的 eat
    &Cat::purr             // [3] Cat 特有的函數(shù)
};

// 內(nèi)存中的對象
Animal* p1 = new Animal();  // 基類對象
// {
//     vptr -> Animal_VTable   // 指向 Animal 的表
//     name: "動物"
// }

Animal* p2 = new Cat();     // 通過基類指針指向派生類對象
// {
//     vptr -> Cat_VTable      // 指向 Cat 的表!
//     name: "咪咪"
//     lives: 9
// }

"看到了嗎?" 老張指著圖說 ??, "每個類都有自己的虛函數(shù)表,這樣做有幾個重要原因:"

  • "多態(tài)的實現(xiàn) ?? - 當(dāng)通過基類指針調(diào)用虛函數(shù)時,實際會根據(jù)對象的真實類型找到正確的函數(shù)版本"
  • "函數(shù)覆寫的支持 ?? - 派生類可以替換掉繼承來的虛函數(shù)實現(xiàn)"
  • "擴展的靈活性 ?? - 派生類可以添加新的虛函數(shù)"

"所以說," 老張繼續(xù)解釋道 ????, "雖然每個類型都有自己的虛函數(shù)表,但同一個類型的所有對象還是共享同一個表。這就是 C++ 多態(tài)的精妙之處!"

"原來如此!" 小王恍然大悟 ??, "這就像每個餐廳分店(類)都有自己的菜單(虛函數(shù)表),但同一個分店的所有服務(wù)員(對象)都用同一份菜單!"

"沒錯!" 老張笑著說 ??, "而且你注意到了嗎?即使用基類指針指向派生類對象,對象的 vptr 也是指向派生類的虛函數(shù)表。這就是為什么我們能通過基類指針正確調(diào)用派生類的函數(shù)實現(xiàn)!"

"這設(shè)計真是太巧妙了!" 小王贊嘆道 ??, "每個類一份虛函數(shù)表,每個對象一個指針,就實現(xiàn)了如此強大的多態(tài)機制!"

老張喝了口咖啡,笑著說道 ??: "今天講的這些只是虛函數(shù)表的基礎(chǔ)知識。要完全理解 C++ 的對象模型,還有很多有趣的話題要探討呢!"

"比如說?" 小王來了興趣 ??

"比如..." 老張神秘地眨眨眼 ??:

  • 虛函數(shù)表是在什么時候、怎么創(chuàng)建的? ???
  • 多重繼承時的虛函數(shù)表是什么樣的? ??
  • 虛繼承又會帶來哪些特殊的內(nèi)存布局? ??
  • 構(gòu)造和析構(gòu)過程中的虛函數(shù)調(diào)用又是怎么處理的? ?

"這些都是非常有趣的話題," 老張站起身來 ??♂?, "不過這些精彩的內(nèi)容,我們下次再聊..."

小王若有所思地點點頭 ??, "感覺 C++ 的每個特性背后都藏著這么多精妙的設(shè)計啊!"

"是啊," 老張笑著說 ??, "這就是為什么即使到今天,研究 C++ 的底層實現(xiàn)依然是那么有趣。" 

責(zé)任編輯:趙寧寧 來源: everystep
相關(guān)推薦

2010-01-18 17:38:54

C++虛函數(shù)表

2022-07-18 15:32:37

C++虛函數(shù)表

2010-02-01 11:22:09

C++虛函數(shù)

2010-01-27 10:36:54

C++虛函數(shù)

2022-05-09 08:37:43

IO模型Java

2024-01-23 10:13:57

C++虛函數(shù)

2024-04-22 13:22:00

虛函數(shù)象編程C++

2011-05-24 16:20:27

虛函數(shù)

2020-06-08 17:35:27

Redis集群互聯(lián)網(wǎng)

2020-12-28 08:36:30

C語言編程泛型

2022-12-14 07:32:40

InnoDBMySQL引擎

2022-01-12 19:59:19

Netty 核心啟動

2010-01-20 18:06:06

C++虛基類

2021-11-10 09:45:06

Lambda表達式語言

2009-03-11 14:42:57

面試求職案例

2023-05-08 07:52:29

JSXReactHooks

2025-09-11 01:55:00

2010-02-05 13:35:19

C++虛析構(gòu)函數(shù)

2024-12-19 14:42:15

C++內(nèi)存泄漏內(nèi)存管理

2011-05-24 16:30:35

虛函數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號

亚洲中文无码av在线| 亚洲国产精品成人综合久久久| 在线激情网站| 国产在线播放一区三区四| 九九久久综合网站| www.日本高清| 成人网ww555视频免费看| 国产精品久久久久久久岛一牛影视| 亚洲iv一区二区三区| 久久黄色小视频| 亚洲午夜久久| 91精品在线麻豆| 欧美爱爱视频免费看| 成人影院免费观看| 懂色av一区二区三区蜜臀| 日韩av免费在线| 黄色一级视频免费| 精品久久不卡| 精品国产凹凸成av人导航| 亚洲 中文字幕 日韩 无码| 污视频免费在线观看| 久久久久久久综合色一本| 国产日产亚洲精品| 国产精品100| 亚洲女同中文字幕| 亚洲欧洲日产国产网站| www.啪啪.com| 亚洲三级电影| 日本韩国精品在线| 青青草国产免费| seseavlu视频在线| kk眼镜猥琐国模调教系列一区二区| 国产美女久久精品| av大片在线免费观看| 欧美日本国产| 久久视频在线播放| 国产午夜精品久久久久久久久| 精品成人自拍视频| 欧美一区二区三区电影| 亚洲欧美久久久久| 成人黄色免费短视频| 亚洲国产综合视频在线观看| 国产精品无码乱伦| 调教视频免费在线观看| 国产亚洲综合在线| 欧美日韩另类综合| 天堂中文在线观看视频| 国产成人超碰人人澡人人澡| 91夜夜揉人人捏人人添红杏| 中文字幕自拍偷拍| 日韩不卡免费视频| 国产成人欧美在线观看| 色婷婷av国产精品| 国产日韩欧美一区在线| 久久免费视频网站| 久久精品国产亚洲av麻豆色欲 | 久久永久免费| 26uuu国产精品视频| 国产情侣在线视频| 99国产精品| 国产91精品久久久久| 日韩 国产 在线| 亚洲美女啪啪| 人人澡人人澡人人看欧美| 日本天堂网在线| 久久只有精品| 国产精品自产拍在线观看| 久久久久久av无码免费看大片| 日韩成人dvd| 成人午夜黄色影院| 精品人妻一区二区三区蜜桃 | 色狠狠av一区二区三区| 免费高清在线观看免费| 日本免费久久| 欧美日韩一级二级三级| 男人的天堂最新网址| 久久久久久久久成人| 精品国产1区二区| 9.1成人看片| 日韩精品看片| 欧美成人午夜激情视频| 日本熟女一区二区| 久久综合导航| 91久久久久久久久久久久久| 精品国产无码一区二区| 9l国产精品久久久久麻豆| 欧美午夜精品久久久久免费视| av在线女优影院| 国产精品第四页| 精品免费久久久久久久| 涩涩av在线| 欧美三级三级三级爽爽爽| 久久婷婷中文字幕| 日本中文字幕在线一区| 夜夜嗨av一区二区三区免费区| 欧美另类videoxo高潮| 亚洲一级高清| 国产精品网红直播| 俄罗斯嫩小性bbwbbw| 久久久精品蜜桃| 50度灰在线观看| 欧美成人h版| 91精品国产麻豆国产自产在线| 中文字幕 日本| 欧美gayvideo| 2018中文字幕一区二区三区| 亚洲一区二区色| 99视频精品在线| 一本一道久久a久久精品综合| 6699嫩草久久久精品影院| 色乱码一区二区三区88| 在线xxxxx| 久久中文字幕二区| 2018日韩中文字幕| 午夜精品小视频| 国产精品久久久久久久久搜平片 | 免费在线观看成人| 久久99国产精品99久久| 4438x成人网全国最大| 色婷婷综合久久久久中文| 黄色av电影网站| 天天综合久久| 国产精品国模在线| 五月天婷婷激情网| 亚洲美女视频在线| 亚洲精品综合在线观看| 国产精品亚洲片在线播放| 欧美—级a级欧美特级ar全黄| 一道本在线视频| 久久精品欧美一区二区三区不卡| 欧美日韩福利在线| 91视频亚洲| yellow中文字幕久久| 亚洲中文字幕无码爆乳av| 91网站黄www| 成人黄色大片网站| 99re热精品视频| 欧美日本高清视频| 国产三级漂亮女教师| 国产精品免费av| av网站在线不卡| 深爱激情综合| 国产91网红主播在线观看| 日本精品久久久久| 亚洲综合免费观看高清完整版在线| 蜜臀一区二区三区精品免费视频| 日韩精品午夜| 国产欧美精品久久久| 95在线视频| 欧美日韩大陆在线| 国产又色又爽又高潮免费| 琪琪一区二区三区| 婷婷亚洲婷婷综合色香五月| 日本在线中文字幕一区二区三区| 亚洲视屏在线播放| 一级久久久久久| 国产精品女上位| 欧洲在线免费视频| 欧美大片专区| 成人自拍网站| 高端美女服务在线视频播放| 亚洲精品第一页| 69xxxx国产| 国产精品久久久久精k8| а 天堂 在线| 欧美a级片网站| 国产精品免费一区二区三区在线观看 | 欧洲午夜精品| 久久精品成人欧美大片古装| 国产片在线播放| 亚洲图片欧美一区| 精品一区二区视频在线观看| 欧美专区一区二区三区| 日韩一区二区三区资源| 亚洲伦理一区二区| 久久久久久久电影一区| 亚洲色偷精品一区二区三区| 色婷婷精品久久二区二区蜜臂av | 欧美日韩国产中字| 最近中文字幕在线mv视频在线| 男女男精品网站| 日本中文字幕一级片| 尤物tv在线精品| 国产免费一区二区三区香蕉精| 尤物视频在线看| 精品无人国产偷自产在线| 这里只有精品9| 亚洲高清视频中文字幕| 五月天综合视频| 国产乱码精品一区二区三区av | 精品欧美乱码久久久久久1区2区| 国产精品国产三级国产专区52| 欧美国产精品中文字幕| 绯色av蜜臀vs少妇| 久久久久网站| 日b视频免费观看| 九九热线有精品视频99| 91九色在线视频| 亚洲黄色网址| 九九视频直播综合网| 美丽的姑娘在线观看免费动漫| 欧美一卡在线观看| 看黄色一级大片| 一区二区久久久| 99精品欧美一区二区| 国产91精品精华液一区二区三区| 牛夜精品久久久久久久| 伊人久久综合| 桥本有菜av在线| 日韩三区视频| 不卡视频一区二区| 欧美一区二区三区婷婷| 欧美有码在线观看| 色呦呦在线观看视频| 一区二区在线视频| 日韩精品视频无播放器在线看| 欧美一区二区高清| 这里只有精品国产| 欧美日韩亚洲成人| 免费在线一区二区三区| 国产精品成人免费精品自在线观看| 国产精品麻豆入口| 国产不卡一区视频| 91丝袜超薄交口足| 蜜桃视频第一区免费观看| 成人一级片网站| 99精品视频免费观看视频| 奇米777四色影视在线看| 99精品电影| 亚洲精品国产精品国自产| 亚洲成a人片77777在线播放| 国产精品一区二区av| 欧美日韩黄色| 成人av电影天堂| 日韩电影精品| 成人h猎奇视频网站| 国产麻豆一区| 国产精品视频1区| 欧美国产日韩电影| 欧洲日本亚洲国产区| a日韩av网址| 2020欧美日韩在线视频| www在线观看黄色| 久久久人成影片一区二区三区观看| 最新超碰在线| 欧美成人一区二区三区电影| 国产在线观看av| 久久精品久久久久| av在线播放国产| 欧美成aaa人片在线观看蜜臀| 伦xxxx在线| 久久亚洲影音av资源网| 麻豆免费在线视频| 久久99国产综合精品女同| 久草在线视频网站| 国产69久久精品成人| 中日韩脚交footjobhd| 日韩美女在线播放| 成人亚洲网站| 成人国产在线视频| 亚洲成人五区| 国产在线资源一区| 亚洲国产合集| 日韩亚洲视频| 国产精品久久久久无码av| 91麻豆天美传媒在线| 欧美三级免费| 一二三四视频社区在线| 亚洲综合精品四区| 制服丝袜综合网| 韩国欧美一区二区| 亚洲一级av无码毛片精品| 久久先锋影音av| 日本高清黄色片| 综合久久久久久| 日本少妇性生活| 欧美伊人久久久久久午夜久久久久| 91片黄在线观看喷潮| 日韩欧美电影一二三| 污视频网站免费观看| 中文字幕亚洲综合久久| 羞羞网站在线免费观看| 91sa在线看| 国产成人精品一区二区三区免费| 91麻豆精品秘密入口| 香蕉久久精品日日躁夜夜躁| 亚洲欧美久久234| 欧美久久99| 国产wwwxx| 国产成人av电影在线| 国产三级av在线播放| 亚洲综合色区另类av| 久久久久久久久久成人| 日韩午夜电影在线观看| 色鬼7777久久| 不卡av电影在线观看| 亚洲风情在线资源| 91精品在线播放| 欧美顶级毛片在线播放| 亚洲午夜精品久久久中文影院av| 亚洲网址在线| 天堂在线资源视频| gogogo免费视频观看亚洲一| 99成人在线观看| 偷拍与自拍一区| av手机免费看| 一道本无吗dⅴd在线播放一区| 国产亚av手机在线观看| 国产精品一区二区久久久久| 国产色噜噜噜91在线精品| 天堂一区二区三区| 日韩网站在线| 欧美体内she精高潮| 欧美国产精品久久| 久久夜色精品亚洲| 欧美一级午夜免费电影| av影片在线看| 88xx成人精品| 一区二区三区视频播放| 亚洲精品一品区二品区三品区| 一区二区91| 美女伦理水蜜桃4| 一区免费观看视频| 中文字幕+乱码+中文乱码www| 日韩大片免费观看视频播放 | 欧美自拍大量在线观看| 亚洲综合影院| 国产日产欧美一区二区| 美女爽到高潮91| 九色porny自拍视频| 精品国产福利在线| 高清乱码毛片入口| 久久久精品中文字幕| 亚洲福利影视| 亚洲欧洲国产日韩精品| 日韩高清中文字幕一区| 蜜桃av免费看| 日本乱人伦一区| 欧美中文在线| 日本成人精品在线| 亚洲精品国产setv| 国产aaa一级片| 91美女片黄在线| 国产精品一区二区6| 亚洲激情电影中文字幕| 99re6在线精品视频免费播放| wwwxx欧美| 亚洲第一伊人| 30一40一50老女人毛片| 黑人巨大精品欧美一区二区| 亚州精品国产精品乱码不99按摩| 97在线免费观看| 西野翔中文久久精品字幕| 丰满爆乳一区二区三区| 91在线你懂得| 手机在线看片1024| 亚洲欧美日韩一区二区在线| 欧美三级网址| 亚洲一卡二卡| 国产一区二区三区香蕉| 久久久91视频| 亚洲国产一区自拍| 亚洲黄色中文字幕| 亚洲人成网站在线播放2019| 九色综合国产一区二区三区| 永久久久久久久| 亚洲国产成人91精品| 在线天堂新版最新版在线8| 日韩欧美国产二区| 精品一区二区免费| 青娱乐国产在线| 亚洲精品电影网站| 日韩制服一区| 无码日本精品xxxxxxxxx| 99久久婷婷国产综合精品 | av中文字幕在线观看第一页 | 欧美性xxxx18| 在线免费黄色| 国产精品有限公司| 丝袜美腿亚洲色图| 美女视频久久久| 日韩av在线免费看| 成人在线中文| 男人的天堂avav| 国产日韩影视精品| 国产视频在线观看视频| 91成人在线视频| 天天色综合色| 香蕉视频黄色在线观看| 欧美久久免费观看| av在线最新| 亚洲精品偷拍视频| 26uuu久久天堂性欧美| 亚洲视频一区在线播放| 97涩涩爰在线观看亚洲| 久久精品高清| 国产精品无码网站| 日韩午夜电影在线观看| 日韩高清在线| 人人干视频在线|