最近研究了一波RTTI,整理了一下知识点,在这里分享一下,下面是目录:
RTTI 是 Run Time Type Information 的缩写,从字面上来理解就是运行时期的类型信息,它的主要作用就是动态判断运行时期的类型。
一般在dynamic_cast和typeid中用到,例如父类B的指针转换子类A的指针,dynamic_cast会判断B究竟是不是A的父类,如果不是,会返回nullptr,相对于强转会更加安全。依据什么判断的呢?就是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;
- }
结果如下:
- 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
上面的代码是正常的一段使用多态的代码,同时也包含了子类指针转基类指针,基类指针转子类指针,从输出结果中可以看到,使用dynamic_cast进行不合理的基类子类指针转换时,会返回nullptr,而强转则不会返回nullptr,运行时肯定就会出现奇奇怪怪的错误,比较难排查。
如果在编译时加上-fno-rtti会怎么样?结果是这样:
- 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编译时,使用typeid或dynamic_cast会报错,即添加-fno-rtti编译会禁止我们使用dynamic_cast和typeid。那为什么要禁止使用他们呢?
1. RTTI的空间成本非常高:每个带有vtable(至少一个虚拟方法)的类都将获得RTTI信息,其中包括类的名称及其基类的信息。此信息用于实现typeid运算符以及dynamic_cast。(大小问题大家可以自己编写代码验证一下)
2. 速度慢,运行时多判断了一层,性能肯定更慢一些。
tips:我这里又将typeid和dynamic_cast去掉重新编译,结果表明添加了-fno-rtti,还是可以正常使用多态,所以大家不用担心rtti的禁用会影响多态的使用。
都知道RTTI信息是存在于虚函数表中,而添加-fno-rtti后代表禁止了RTTI,那虚函数表中还会有rtti信息吗?
我这里使用clang的命令查看一下虚函数表:
- 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()
通过结果可以看到,即使添加了-fno-rtti,虚函数表中还是会存在RTTI指针,但是我查看很多文档都说rtti会导致可执行文件的体积增大一些(毕竟-fno-rtti最大的目的就是为了减小代码和可执行文件的大小),所以我估计指针指向的块里面可能什么信息都没有,具体就不得而知了。