C++ typeid 和虛函數(shù)詳解
typeid 和虛函數(shù)
前面咱們講到 typeid 的操作返回值是 type_info 對象的引用,然后輸出返回值的地址是相同的,測試代碼如下:
#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
virtual
void test(){
cout << "Base::test" << endl;
}
};
class Derived : public Base{
public:
void test(){
cout << "Derived::test" << endl;
}
virtual
~Derived(){
cout << "Derived::~Derived" << endl;
}
};
int main()
{
Base* pBase = new Base();
Base* pBase2 = new Derived();
Derived* pDerive = new Derived();
//typeid(pBase2) 和 typeid(pDerive) 返回地址相同
cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
return 0;
}
output信息:
typeid(pBase2) = 0x55dd724c6d48 typeid(pDerive) = 0x55dd724c6d48
也就是說,0x55dd724c6d48 就是 Derived 類編譯之后的類標(biāo)識(shí)(type_info)數(shù)據(jù)信息!是否真的如此,咱們可以添加一下代碼測試:
int main()
{
Base* pBase = new Base();
Base* pBase2 = new Derived();
Derived* pDerive = new Derived();
//typeid(pBase2) 和 typeid(pDerive) 返回地址相同
cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
//class Base type_info 地址
cout << "typeid(Base) = " << &typeid(Base) << endl;
//class Derive type_info 地址
cout << "typeid(Derived) = " << &typeid(Derived) << endl;
//指針類型推導(dǎo)
cout << "point ---- typeid(pBase2) = " << &typeid(pBase2) << " typeid(pDerive) = "<< &typeid(pDerive) << endl;
return 0;
}
ouput信息:
typeid(pBase2) = 0x562309345d48 typeid(pDerive) = 0x562309345d48
typeid(Base) = 0x562309345d60
typeid(Derived) = 0x562309345d48
point ---- typeid(pBase2) = 0x562309345d28 typeid(pDerive) = 0x562309345d08
可以看到,Derived 類的 type_info 信息的地址就是 0x558a4dec7d48 !要注意的一點(diǎn):直接對指針類型進(jìn)行操作,并不能返回正確的原始類型。
好了嘛,那 typeid 到底是咋從虛函數(shù)表找到這個(gè)地址的呢?如果大家看過我之前的 深入理解new[]和delete[]_master-計(jì)算機(jī)科學(xué)專欄-CSDN博客 一文,應(yīng)該就能夠想到是不是C++編譯器對虛函數(shù)表進(jìn)行構(gòu)造的過程中是不是也一樣,做了地址偏移呢?
咱們看看上面代碼的匯編信息:

通過查看匯編信息,我們得到以下結(jié)論:
虛函數(shù)表中確實(shí)存有typeinfo信息(第一個(gè)虛函數(shù)的地址偏移 -1 即是)typeinfo信息是區(qū)分指針類型是的(指針類型有前綴P,例如 P4Base、P7Derived)
然后,我們仔細(xì)觀察四個(gè) typeinfo 類(Derived*、 Base*、Derived、Base),每個(gè)typeinfo 類都有一個(gè)虛函數(shù)表,繼承自 vtable for __cxxabiv1::******* ,后面的信息會(huì)不一樣。這里對該信息做一下簡單說明:
對于啟用了 RTTI 的類來說會(huì)繼承 __cxxabiv1 里的某個(gè)類所有的基礎(chǔ)類(沒有父類的類)都繼承于_class_type_info所有的基礎(chǔ)類指針都繼承自 __pointer_type_info所有的單一繼承類都繼承自 __si_class_type_info所有的多繼承類都繼承自 __vmi_class_type_info以typeinfo for Derived為例:
然后是指向存儲(chǔ)類型名字的指針,
如果有繼承關(guān)系,則最后是指向父類的 typeinfo 的記錄。
所以,如果是正常調(diào) typeinfo 基類(_class_type_info、__pointer_type_info、__si_class_type_info、__vmi_class_type_info)的方法,應(yīng)該會(huì)動(dòng)態(tài)調(diào)到 type_info 的繼承類 (typeinfo for Derived*、typeinfo for Base*、typeinfo for Derived、typeinfo for Base)的方法。
但是,typeid 操作指針類型時(shí)并不是這樣,說明C++編譯器底層有特殊處理!
調(diào)試以下代碼:
cout << typeid(*pBase2).name();
cout << typeid(*pDerive).name();
cout << typeid(pBase2).name();
cout << typeid(pDerive).name();
通過匯編信息,可以看到這里并沒有做任何動(dòng)態(tài)調(diào)用的邏輯,而是直接返回該指針類型的typeinfo信息,這也就解釋了為什么 typeid 操作指針和操作對象的結(jié)果不一樣!

那么我們在使用typeid時(shí),如果要獲取到真實(shí)對象類型,應(yīng)該要將指針去掉!
為了驗(yàn)證我們前面的結(jié)論: 虛函數(shù)表中確實(shí)存有typeinfo信息(第一個(gè)虛函數(shù)的地址偏移 -1 即是),咱們可以直接通過指針的方式操作虛函數(shù)表!
測試代碼如下:
#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
virtual
void test(){
cout << "Base::test" << endl;
}
};
class Derived : public Base{
public:
void test(){
cout << "Derived::test" << endl;
}
virtual
~Derived(){
cout << "Derived::~Derived" << endl;
}
};
typedef void (*FUNPTR)();
type_info* getTypeInfo(unsigned long ** vtbl){
type_info* typeinfo = (type_info*)((unsigned long)vtbl[-1]);
return typeinfo;
}
void visitVtbl(unsigned long ** vtbl, int count)
{
cout << vtbl << endl;
cout << "\t[-1]: " << (unsigned long)vtbl[-1] << endl;
typedef void (*FUNPTR)();
for (int i = 0; vtbl[i] && i < count; ++i)
{
cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
FUNPTR func = (FUNPTR)vtbl[i];
func();
}
}
int main()
{
Base* pBase = new Base();
Base* pBase2 = new Derived();
Derived* pDerive = new Derived();
//這里去遍歷虛函數(shù)表
visitVtbl((unsigned long **)*(unsigned long **)pBase2, 2);
//獲取虛函數(shù)表-1位置的typeinfo地址
cout << "pDerive = " << getTypeInfo((unsigned long **)*(unsigned long **)pDerive) << " "
<< getTypeInfo((unsigned long **)*(unsigned long **)pDerive)->name() << endl;
//獲取虛函數(shù)表-1位置的typeinfo地址
cout << "pBase2 = " << getTypeInfo((unsigned long **)*(unsigned long **)pBase2) << " "
<< getTypeInfo((unsigned long **)*(unsigned long **)pBase2)->name() << endl;
return 0;
}
這里要注意的一點(diǎn)是,遍歷虛函數(shù)表 visitVtbl 方法的第2個(gè)參數(shù),自己控制不要越界,此外,還要注意調(diào)用的順序,如果先調(diào)用了虛析構(gòu)函數(shù),會(huì)導(dǎo)致內(nèi)存錯(cuò)誤!
output信息:
0x5620022edd10
[-1]: 94695475567936
[0]: 0x5620022eb5fa -> Derived::test
[1]: 0x5620022eb636 -> Derived::~Derived
pDerive = 0x5620022edd40 7Derived
pBase2 = 0x5620022edd40 7Derived
通過直接訪問虛函數(shù)表-1位置,我們可以看到輸出的日志信息與我們前面的結(jié)論是一致的!也即是C++編譯器給我們做了偏移操作(在-1的位置存儲(chǔ)了type_info信息,實(shí)例化對象中的虛函數(shù)表地址是偏移之后的地址)。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語言中send()函數(shù)和sendto()函數(shù)的使用方法
這篇文章主要介紹了C語言中send()函數(shù)和sendto()函數(shù)的使用方法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
C++生成隨機(jī)數(shù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了C++生成隨機(jī)數(shù)的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
簡單掌握C++編程中的while與do-while循環(huán)語句使用
這篇文章主要介紹了C++編程中的while與do-while循環(huán)語句使用,區(qū)別就是while是先判斷再執(zhí)行,而do-while是先執(zhí)行再判斷,需要的朋友可以參考下2016-01-01
opencv實(shí)現(xiàn)圖片與視頻中人臉檢測功能
這篇文章主要為大家詳細(xì)介紹了opencv實(shí)現(xiàn)圖片與視頻中人臉檢測功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01


