C++ typeid实现原理

最近看了boost::any类源码,其实现主要依赖typeid操作符。很好奇这样实现的时间和空间开销有多大,决定探一下究竟。

VS2008附带的type_info类只有头文件,没有源文件,声明如下:

[cpp] view plaincopy

  1. class type_info {
  2. public:
  3. virtual ~type_info();
  4. _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& rhs) const;
  5. _CRTIMP_PURE bool __CLR_OR_THIS_CALL operator!=(const type_info& rhs) const;
  6. _CRTIMP_PURE int __CLR_OR_THIS_CALL before(const type_info& rhs) const;
  7. _CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
  8. _CRTIMP_PURE const char* __CLR_OR_THIS_CALL raw_name() const;
  9. private:
  10. void *_m_data;
  11. char _m_d_name[1];
  12. __CLR_OR_THIS_CALL type_info(const type_info& rhs);
  13. type_info& __CLR_OR_THIS_CALL operator=(const type_info& rhs);
  14. _CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base(const type_info *,__type_info_node* __ptype_info_node);
  15. _CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor(type_info *);
  16. };

测试代码:

[cpp] view plaincopy

  1. #include <iostream>
  2. using namespace std;
  3. class Object
  4. {
  5. };
  6. int main()
  7. {
  8. Object obj;
  9. cout << "type name:" << typeid(obj).name() << endl;
  10. cout << "type raw name:" << typeid(obj).raw_name() << endl;
  11. if(typeid(obj) == typeid(Object))
  12. {
  13. cout << "type is equal" << endl;
  14. }
  15. else
  16. {
  17. cout << "type is not equal" << endl;
  18. }
  19. return 0;
  20. }

输出:

type name:class Object
type raw name:[email protected]@
type is equal

在解释每个函数的实现原理前先开看type_info类的存储方式。

typeid返回的是type_info的引用,这个类不能拷贝,也不能自己构造,所以每个类最多只有一个type_info的数据,这个数据存放在哪里的呢?

用UltraEdit打开exe文件,搜索“Object”,能找到这个字符串。再用PE工具打开这个exe,发现这个字符串属于data节(这是可读可写的全局数据段)。再把有typeid的代码都注释,PE文件中没有了这个字符串。得出一个结论:

编译器会为每一种typeid操作的类型生成一份保存在数据段的type_info数据。

 

这份数据有多大呢?看下面这段代码:

[cpp] view plaincopy

  1. #include <iostream>
  2. using namespace std;
  3. class Object
  4. {
  5. };
  6. int main()
  7. {
  8. const type_info* p = &typeid(Object);
  9. cout << p << endl;
  10. return 0;
  11. }

在cout那一行下断点,查看到p的值为:

再看下这个类的声明,析构函数为virtual类型的,所以p的头四字节为虚函数表。p+4为_m_data,void*类型,四个字节,调试时发现都是0,还不清楚其表示什么。

p+8为_m_d_name,char类型数组,存储的是raw_name,每种类型的raw_name大小不定长,所以数组长度为1。现在type_info的存储结构已经一目了然:

每种类型的type_info数据长度依赖于类型名称,至少9个字节。

现在假设一个复杂的工程里面有50个类型用了typeid操作符,平均每个type_info长度为24,这些数据增加的PE大小为1200B,就1K左右。而现在的PE动辄几十M,所以这点空间开销根本不算什么。

再看看这些函数调用的开销:

  • raw_name函数直接返回_m_d_name的地址,非常快;
  • name函数将_m_d_name存储的字符串解码成实际的名称,也是很快;
  • ==操作符是比较raw_name是否相等,也是很快。

读者可能会有两点疑惑:

  1. 存储的时候为什么不直接存储成name呢?我想最大的原因是节省空间,比如double的raw_name为".N",name为"double"多了四字节。
  2. ==操作符为什么不直接比较两个type_info引用的地址是否相等呢?我也很疑惑,我看汇编码发现它是比较raw_name。

备注:C++并没有规定typeid实现标准,各个编译器可能会不一样,上述分析过程基于VS2008自带的编译器。

总结:typeid带来的时间和空间开销是非常小的,不过使用的时候尽量不要违背开放封闭原则。

http://blog.csdn.net/passion_wu128/article/details/38441633

时间: 2024-08-08 03:35:46

C++ typeid实现原理的相关文章

C++杂记:运行时类型识别(RTTI)与动态类型转换原理

运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. 静态类型的情形 C++中支持使用typeid关键字获取对象类型信息,它的返回值类型是const std::type_info&,例: #include <typeinfo> #include <cassert> struct B {} b, c; struct D : B {

【转载】C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理

原文:C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理 运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. 静态类型的情形 C++中支持使用typeid关键字获取对象类型信息,它的返回值类型是const std::type_info&,例: #include <typeinfo> #include <cassert>

C++ 类型判断 typeid

class A{    virtual void f(){};}; class B: public A{}; 在main方法中验证 Person* p = new son();    if (typeid(*p) == typeid(son))    {        std::cout << "equal" << std::endl;    }    else    {        std::cout << "not equal&quo

C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理

运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. 静态类型的情形 C++中支持使用typeid关键字获取对象类型信息,它的返回值类型是const std::type_info&,例: #include <typeinfo> #include <cassert> struct B {} b, c; struct D : B {

【C/C++学院】0823-静态联合编译与动态联合编译/父类指针子类指针释放/虚函数/纯虚函数概念以及虚析构函数/抽象类与纯虚函数以及应用/虚函数原理/虚函数分层以及异质链表/类模板的概念以及应用

静态联合编译与动态联合编译 #include <iostream> #include <stdlib.h> //散列 void go(int num) { } void go(char *str) { } //class //::在一个类中 class A { public: void go(int num) { } void go(char *str) { } }; void main() { ///auto p = go;编译的阶段,静态联编 void(*p1)(char *s

C++ TypeId简介与使用

简介 TypeId 返回一个变量或数据类型的"类型". 使用场景 用法如下:     cout<<typeid(int).name()<<endl;         int a;     cout<<typeid(a).name()<<endl; 输出如下: int int 注意事项 如果有类A,且有虚函数,类B,C,D都是从类A派生的,且都重定义了类A中的虚函数,这时有类A的指针p,再把对象类B的对象的地址赋给指针p,则typeid(p

(转)DEDECMS模板原理、模板标签学习 - .Little Hann

本文,小瀚想和大家一起来学习一下DEDECMS中目前所使用的模板技术的原理: 什么是编译式模板.解释式模板,它们的区别是什么? 模板标签有哪些种类,它们的区别是什么,都应用在哪些场景? 学习模板的机制原理对我们修复目前CMS中常出现的模板类代码执行的漏洞能起到怎样的帮助? 带着这些问题,我们进入今天的代码研究,just hacking for fun!! 文章主要分为以下几个部分 1. 模板基本知识介绍 2. 怎么使用模板机制.模板标签的使用方法 3. DEDE模板原理学习 1) 编译式模板 2

JVM原理讲解和调优

一.什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. Java语言的一个非常重要的特点就是与平台的无关性.而使用Java虚拟机是实现这一特点的关键.一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码.而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译.Java语言使用Java虚拟机屏蔽了与具体平台相关的信息

小米手环 / 运动手环 记步功能原理

很多朋友是第一次接触像小米手环这类运动计步产品,对于那么轻盈小巧的手环能够精准计步,甚至能详细完整的记录睡眠时间觉得非常神奇,本文就和大家详细说说在看不见的小米手环背板下,它是怎么工作的. 1. 手机上的运动步数是怎么来的? A:简单来说:小米手环能够精准计步由硬件和软件算法两方面组成,缺一不可. 硬件 是指小米手环里内置的那枚强悍的三轴加速度传感器ADXL362 (注1),军用级,大家知道想要达到军用级,这得有多苛刻.其实三轴加速度传感器不神秘,在大多数中高档手机里都有配备加速度传感器,只是在