C++虛函數(shù)表和虛析構(gòu)介紹
1、虛函數(shù)表
虛函數(shù)表是C++實(shí)現(xiàn)多態(tài)的基礎(chǔ),多態(tài)是面向?qū)ο蟮娜筇匦灾?,多態(tài)有利于提高代碼的可讀性,便于后期代碼的擴(kuò)展和維護(hù)。我們都知道多態(tài)的實(shí)現(xiàn)是基于虛函數(shù)表,那么虛函數(shù)表是什么時(shí)候創(chuàng)建的呢?虛函數(shù)表是怎么實(shí)現(xiàn)多態(tài)的功能的呢?
首先應(yīng)該明確多態(tài)也稱為動(dòng)態(tài)多態(tài),他是在程序運(yùn)行時(shí)候確定函數(shù)地址的,也就是程序在運(yùn)行時(shí),如果類成員函數(shù)加了virtual
關(guān)鍵字,就會(huì)建立一個(gè)虛函數(shù)指針(vfptr
)指針指向一個(gè)虛函數(shù)表,這個(gè)虛函數(shù)表就保存了虛函數(shù)的地址,子類繼承父類也自然繼承了虛函數(shù)指針,當(dāng)子類重寫父類的虛函數(shù)時(shí),虛函數(shù)指針?biāo)赶虻奶摵瘮?shù)表中的虛函數(shù)地址就會(huì)被覆蓋,替換成子類的虛函數(shù)地址。也就是通過父類的虛函數(shù)指針找到了子類的虛函數(shù)地址,進(jìn)而執(zhí)行這個(gè)函數(shù)。
下面我們通過代碼進(jìn)行詳細(xì)說明:
#include <iostream> using namespace std; class Base{ public: void func(){ cout << "Base func" << endl; } }; class Son: public Base{ void func(){ cout << "Son func" << endl; } }; void test(Base& base) { base.func(); } int main () { Son son; cout << "sizeof(Base) = " << sizeof(Base) << endl; cout << "sizeof(Son) = " << sizeof(Son) << endl; test(son); system("pause"); return 0; }
代碼運(yùn)行結(jié)果為:
因?yàn)楹瘮?shù)成員不占用類的大小,所以對(duì)Base類和Son類輸出大小,都是一個(gè)字節(jié),這一個(gè)字節(jié)是為了可以實(shí)例化類,通過引用基類引用派生類,調(diào)用func
函數(shù),函數(shù)調(diào)用了基類的func
,那么如果我們加上virtual
關(guān)鍵字后,就不是這種情況了。
#include <iostream> using namespace std; class Base{ public: virtual void func(){ cout << "Base func" << endl; } }; class Son: public Base{ void func(){ cout << "Son func" << endl; } }; void test(Base& base) { base.func(); } int main () { Son son; cout << "sizeof(Base) = " << sizeof(Base) << endl; cout << "sizeof(Son) = " << sizeof(Son) << endl; test(son); system("pause"); return 0; }
代碼運(yùn)行結(jié)果為:
可以看到加了virtual
關(guān)鍵字后,父類和子類的大小都變成了四字節(jié),這是因?yàn)樯闪颂摵瘮?shù)指針,指針指向虛函數(shù)表,虛函數(shù)表存儲(chǔ)了虛函數(shù)地址,繼承了父類的子類重寫了虛函數(shù),虛函數(shù)表中的函數(shù)地址被替換,再次調(diào)用虛函數(shù)就是調(diào)用了子類的函數(shù)func
。
2、虛析構(gòu)
虛析構(gòu)主要是為了解決子類中有屬性開辟到堆區(qū),父類指針調(diào)用函數(shù)時(shí),無法調(diào)用到子類的析構(gòu)代碼,導(dǎo)致子類堆區(qū)內(nèi)存無法釋放。
首先我們看一下子類堆區(qū)內(nèi)存開辟,通過父類指針來調(diào)用函數(shù),捕捉他們的構(gòu)造函數(shù)和析構(gòu)函數(shù)看下運(yùn)行結(jié)果:
#include <iostream> using namespace std; class Base{ public: Base(){ cout << "Base 的構(gòu)造函數(shù)調(diào)用" << endl; } ~Base(){ cout << "Base 的析構(gòu)函數(shù)調(diào)用" << endl; } virtual void func(){ cout << "Base func" << endl; } }; class Son: public Base{ public: Son(int val):m_val(new int (val)) { cout << "Son 的構(gòu)造函數(shù)調(diào)用" << endl; } ~Son(){ cout << "Son 的析構(gòu)函數(shù)調(diào)用" << endl; if (m_val != NULL) { delete m_val; cout << "Son 析構(gòu)函數(shù)的堆內(nèi)存釋放" << endl; m_val = NULL; } } void func(){ cout << "Son func" << endl; } void funcTest(){ cout << "funcTest 函數(shù)調(diào)用" << endl; } int* m_val = NULL; }; void test() { Base *base = new Son(10); base->func(); //base->funcTest(); //無法調(diào)用,因?yàn)樘摵瘮?shù)表中不能找到這個(gè)函數(shù)的地址 delete base; base = NULL; } int main () { test(); system("pause"); return 0; }
代碼運(yùn)行結(jié)果為:
可以明確,通過父類指針來調(diào)用函數(shù)的時(shí)候,無法調(diào)用Son類的析構(gòu)函數(shù),在Son
類在堆區(qū)上申請(qǐng)的內(nèi)存就無法釋放,造成內(nèi)存泄漏。Son
類的析構(gòu)函數(shù)不能調(diào)用的主要原因就是在虛函數(shù)表中找不到Son
的析構(gòu)函數(shù)地址,解決辦法就是把Base類的寫成虛析構(gòu)函數(shù)或者純虛析構(gòu)函數(shù),
下面給出Base類為純虛析構(gòu)函數(shù)的代碼和運(yùn)行結(jié)果:
#include <iostream> using namespace std; class Base{ public: Base(){ cout << "Base 的構(gòu)造函數(shù)調(diào)用" << endl; } virtual ~Base() = 0; virtual void func(){ cout << "Base func" << endl; } }; Base :: ~Base(){ cout << "Base 的析構(gòu)函數(shù)調(diào)用" << endl; } class Son: public Base{ public: Son(int val):m_val(new int (val)) { cout << "Son 的構(gòu)造函數(shù)調(diào)用" << endl; } ~Son(){ cout << "Son 的析構(gòu)函數(shù)調(diào)用" << endl; if (m_val != NULL) { delete m_val; cout << "Son 析構(gòu)函數(shù)的堆內(nèi)存釋放" << endl; m_val = NULL; } } void func(){ cout << "Son func" << endl; } void funcTest(){ cout << "funcTest 函數(shù)調(diào)用" << endl; } int* m_val = NULL; }; void test() { Base *base = new Son(10); base->func(); //base->funcTest(); //無法調(diào)用,因?yàn)樘摵瘮?shù)表中不能找到這個(gè)函數(shù)的地址 delete base; base = NULL; } int main () { test(); system("pause"); return 0; }
代碼運(yùn)行結(jié)果為:
可以看到只要把Base
類的析構(gòu)函數(shù)寫成虛析構(gòu)函數(shù)或純虛析構(gòu)函數(shù),通過父類指針調(diào)用函數(shù),子類的析構(gòu)代碼會(huì)被調(diào)用,子類堆區(qū)內(nèi)存得到釋放。
到此這篇關(guān)于C++虛函數(shù)表和虛析構(gòu)介紹的文章就介紹到這了,更多相關(guān)C++虛函數(shù)表和虛析構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++動(dòng)態(tài)內(nèi)存管理與智能指針的相關(guān)知識(shí)點(diǎn)
為了更容易同時(shí)也更安全地使用動(dòng)態(tài)內(nèi)存,新的標(biāo)準(zhǔn)庫提供了兩種智能指針(smart pointer)類型來管理對(duì)象,下面這篇文章主要給大家介紹了關(guān)于c++動(dòng)態(tài)內(nèi)存管理與智能指針的相關(guān)知識(shí)點(diǎn),需要的朋友可以參考下2022-03-03淺析VSCode tasks.json中的各種替換變量的意思 ${workspaceFolder} ${file} ${
這篇文章主要介紹了關(guān)于VSCode tasks.json中的各種替換變量的意思 ${workspaceFolder} ${file} ${fileBasename} ${fileDirname}等,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03基于C中一個(gè)行壓縮圖的簡單實(shí)現(xiàn)代碼
首先簡單說一下什么是行壓縮圖,其實(shí)嚴(yán)格意義上應(yīng)該是行壓縮矩陣2013-05-05C++?string如何獲取文件路徑文件名、文件路徑、文件后綴(兩種方式)
這篇文章主要介紹了C++?string如何獲取文件路徑文件名、文件路徑、文件后綴(兩種方式),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2023-06-06C++實(shí)現(xiàn)反轉(zhuǎn)鏈表的兩種方法
本文主要介紹了C++實(shí)現(xiàn)反轉(zhuǎn)鏈表的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02項(xiàng)目之C++如何實(shí)現(xiàn)數(shù)據(jù)庫連接池
這篇文章主要介紹了項(xiàng)目之C++如何實(shí)現(xiàn)數(shù)據(jù)庫連接池問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Qt圖形圖像開發(fā)之曲線圖表庫QChart編譯安裝詳細(xì)方法與使用實(shí)例
這篇文章主要介紹了Qt圖形圖像開發(fā)之曲線圖表庫QChart編譯安裝詳細(xì)方法與使用實(shí)例,需要的朋友可以參考下2020-03-03