研究了一波RTTI,你會(huì)了嗎
最近研究了一波RTTI,整理了一下知識(shí)點(diǎn),在這里分享一下,下面是目錄:
RTTI 是 Run Time Type Information 的縮寫(xiě),從字面上來(lái)理解就是運(yùn)行時(shí)期的類(lèi)型信息,它的主要作用就是動(dòng)態(tài)判斷運(yùn)行時(shí)期的類(lèi)型。
一般在dynamic_cast和typeid中用到,例如父類(lèi)B的指針轉(zhuǎn)換子類(lèi)A的指針,dynamic_cast會(huì)判斷B究竟是不是A的父類(lèi),如果不是,會(huì)返回nullptr,相對(duì)于強(qiáng)轉(zhuǎn)會(huì)更加安全。依據(jù)什么判斷的呢?就是RTTI。
先看下面這段代碼:
- #include <iostream>
- using std::cout;
- using std::endl;
- class Base
- {
- public:
- int a;
- int b;
- Base()
- {
- cout << this << " Base \n";
- }
- virtual void func()
- {
- cout << this << " hello Base \n";
- };
- void basefunc()
- {
- cout << this << " hello basefunc \n";
- }
- };
- class BaseBB
- {
- public:
- int d;
- int c;
- BaseBB()
- {
- cout << this << " BaseBB \n";
- }
- virtual void func()
- {
- cout << this << " hello BaseBB \n";
- }
- };
- class Derive : public Base
- {
- public:
- Derive()
- {
- cout << this << " Derive \n";
- }
- void func() override
- {
- cout << this << " hello Derive \n";
- }
- };
- int main()
- {
- Derive *d = new Derive;
- typeid(d);
- d->func();
- Base *b = static_cast<Base *>(d);
- b->func();
- b->basefunc();
- Derive *b1 = dynamic_cast<Derive *>(b);
- Derive *b2 = static_cast<Derive *>(b);
- b1->func();
- b2->func();
- BaseBB *b3 = dynamic_cast<BaseBB *>(b);
- BaseBB *b4 = reinterpret_cast<BaseBB *>(b);
- cout << d << " " << b << " " << b1 << " " << b2 << " " << b3 << " " << b4 << endl;
- return 0;
- }
結(jié)果如下:
- clang++ test_rtti.cc -std=c++11;./a.out
- 0x7fe80ac05920 Base
- 0x7fe80ac05920 Derive
- 0x7fe80ac05920 hello Derive
- 0x7fe80ac05920 hello Derive
- 0x7fe80ac05920 hello basefunc
- 0x7fe80ac05920 hello Derive
- 0x7fe80ac05920 hello Derive
- 0x7fe80ac05920 0x7fe80ac05920 0x7fe80ac05920 0x7fe80ac05920 0x0 0x7fe80ac05920
上面的代碼是正常的一段使用多態(tài)的代碼,同時(shí)也包含了子類(lèi)指針轉(zhuǎn)基類(lèi)指針,基類(lèi)指針轉(zhuǎn)子類(lèi)指針,從輸出結(jié)果中可以看到,使用dynamic_cast進(jìn)行不合理的基類(lèi)子類(lèi)指針轉(zhuǎn)換時(shí),會(huì)返回nullptr,而強(qiáng)轉(zhuǎn)則不會(huì)返回nullptr,運(yùn)行時(shí)肯定就會(huì)出現(xiàn)奇奇怪怪的錯(cuò)誤,比較難排查。
如果在編譯時(shí)加上-fno-rtti會(huì)怎么樣?結(jié)果是這樣:
- clang++ test_rtti.cc -std=c++11 -fno-rtti
- test_rtti.cc:60:5: error: use of typeid requires -frtti
- typeid(d);
- ^
- test_rtti.cc:65:18: error: use of dynamic_cast requires -frtti
- Derive *b1 = dynamic_cast<Derive *>(b);
- ^
- test_rtti.cc:69:18: error: use of dynamic_cast requires -frtti
- BaseBB *b3 = dynamic_cast<BaseBB *>(b);
- ^
- 3 errors generated.
可以看到,加上了-fno-rtti編譯時(shí),使用typeid或dynamic_cast會(huì)報(bào)錯(cuò),即添加-fno-rtti編譯會(huì)禁止我們使用dynamic_cast和typeid。那為什么要禁止使用他們呢?
1. RTTI的空間成本非常高:每個(gè)帶有vtable(至少一個(gè)虛擬方法)的類(lèi)都將獲得RTTI信息,其中包括類(lèi)的名稱(chēng)及其基類(lèi)的信息。此信息用于實(shí)現(xiàn)typeid運(yùn)算符以及dynamic_cast。(大小問(wèn)題大家可以自己編寫(xiě)代碼驗(yàn)證一下)
2. 速度慢,運(yùn)行時(shí)多判斷了一層,性能肯定更慢一些。
tips:我這里又將typeid和dynamic_cast去掉重新編譯,結(jié)果表明添加了-fno-rtti,還是可以正常使用多態(tài),所以大家不用擔(dān)心rtti的禁用會(huì)影響多態(tài)的使用。
都知道RTTI信息是存在于虛函數(shù)表中,而添加-fno-rtti后代表禁止了RTTI,那虛函數(shù)表中還會(huì)有rtti信息嗎?
我這里使用clang的命令查看一下虛函數(shù)表:
- clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -fno-rtti -c test_rtti.cc
- test_rtti.cc:51:17: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
- void func() override
- ^
- Original map
- void Derive::func() -> void Base::func()
- Vtable for 'Derive' (3 entries).
- 0 | offset_to_top (0)
- 1 | Derive RTTI
- -- (Base, 0) vtable address --
- -- (Derive, 0) vtable address --
- 2 | void Derive::func()
- VTable indices for 'Derive' (1 entries).
- 0 | void Derive::func()
通過(guò)結(jié)果可以看到,即使添加了-fno-rtti,虛函數(shù)表中還是會(huì)存在RTTI指針,但是我查看很多文檔都說(shuō)rtti會(huì)導(dǎo)致可執(zhí)行文件的體積增大一些(畢竟-fno-rtti最大的目的就是為了減小代碼和可執(zhí)行文件的大小),所以我估計(jì)指針指向的塊里面可能什么信息都沒(méi)有,具體就不得而知了。