当前位置:   article > 正文

C++运行阶段类型识别(Runtime Type Identification)(C++新特性)_c++运行时类型识别

c++运行时类型识别

编译时开销

编译器在编译代码时,主要进行:词法分析(识别单词,确认词类:标识符、关键字、字符串、数字等等)、语法分析(识别短语和句型的语法属性,生成语法树)、语义分析(确认单词,句型的语义特征,如加减乘除等)、代码优化和代码生成等。模板、类层次结构、强制类型检查等新特性,以及大量使用了这些新特性的STL标准库都增加了编译器负担。

运行时开销

C++中有可能引入额外运行时开销的新特性包括:

  1. 虚基类
  2. 虚函数
  3. RTTI(dynamic_cast和typeid)
  4. 异常
  5. 对象的构造和析构

RTTI

RTTI是运行阶段类型识别(Runtime Type Identification)的简称。这是新添加到C++的特性之一。

RTTI旨在为程序在运行阶段确定对象类型提供一种标准方式,很多类库已经为其类对象提供了实现了这种功能的方式,但由于C++内部并不支持,因此各个厂商的机制通常互不兼容。

RTTI的用途

假设有一个类层次结构,其中类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。这样便可以调用这样的函数:在处理一些信息后,选择一个类,并创建这个类型的对象,然后返回它的地址,而该地址可以被赋给基类指针。但是如何知道指针指向的是哪个对象呢?

在回答这个问题之前,先考虑为何要知道类型:

  1. 希望调用类方法的正确版本,这种情况下,只要该函数是类层次结构中所有成员都拥有的虚函数,就可以不需要知道对象的类型。
  2. 但派生对象可能包含不是继承而来的方法,这种情况下,只有某些类型对象可以使用该方法。
  3. 出于调试目的,想跟踪生成对象的类型。

对于后两种情况,RTTI提供解决方案。

RTTI的工作原理

C++有三个支持RTTI的元素:

  1. dynamic_cast运算符,如果可能的话,dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,否则该运算符返回0——空指针。
  2. typeid运算符返回一个指出对象类型的值。
  3. type_info结构存储了有关特定类型的信息。

只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针(多态)

RTTI只适用于包含虚函数的类(实现多态)

dynamic_cast运算符

dynamic_cast运算符是最常用的RTTI组件,它不能回答“指针是指向哪类的对象”这样的问题,但是能够回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题

  1. #include <iostream>
  2. #include<cstdlib>
  3. #include<ctime>
  4. using namespace std;
  5. class Grand
  6. {
  7. private:
  8. int hold;
  9. public:
  10. Grand(int h = 0) :hold(h) {}
  11. virtual void Speak() const {
  12. cout << "I am a grand class!" << endl;
  13. }
  14. virtual int Value() const {
  15. return hold;
  16. }
  17. };
  18. class Superb :public Grand
  19. {
  20. public:
  21. Superb(int h = 0) :Grand(h) {}
  22. //虽然没有声明为virtual,但默认为virtual
  23. void Speak() const {
  24. cout << "I am a superb class!" << endl;
  25. }
  26. virtual void Say() const {
  27. cout << "I hold the superb value of "<<Value() << endl;
  28. }
  29. };
  30. class Magnificent :public Superb
  31. {
  32. private:
  33. char ch;
  34. public:
  35. Magnificent(int h = 0, char c = 'A') :Superb(h), ch(c) {}
  36. //两个都是虚函数
  37. void Speak() const {
  38. cout << "I am a magnificent class!" << endl;
  39. }
  40. void Say() const {
  41. cout << "I hold the character "<<ch<<" and the integer "<<Value() << endl;
  42. }
  43. };
  44. //随机产生一种对象
  45. Grand * GetOne() {
  46. Grand *p;
  47. switch (rand()%3)
  48. {
  49. case 0: p = new Grand(rand() % 100); return p;;
  50. case 1: p = new Superb(rand() % 100); return p;;
  51. case 2: p = new Magnificent(rand() % 100,'A'+ rand() % 26); return p;
  52. }
  53. }
  54. int main(void) {
  55. srand(time(0));
  56. Grand *pg;
  57. Superb *ps;
  58. for (int i = 0; i < 5; i++) {
  59. pg = GetOne();
  60. pg->Speak();
  61. if (ps = dynamic_cast<Superb *>(pg))
  62. ps->Say();
  63. else
  64. {
  65. cout << "I get nothing" << endl;
  66. }
  67. }
  68. return 0;
  69. }
  70. /*
  71. *
  72. *I am a superb class!
  73. I hold the superb value of 42
  74. I am a magnificent class!
  75. I hold the character G and the integer 90
  76. I am a grand class!
  77. I get nothing
  78. I am a superb class!
  79. I hold the superb value of 53
  80. I am a magnificent class!
  81. I hold the character D and the integer 31
  82. */
  1. Grand *pg = new Grand;
  2. Grand *ps = new Superb;
  3. Grand *pm = new Magnificent;
  4. Magnificent *p1 = (Magnificent*)pm; //#1
  5. Magnificent *p2 = (Magnificent*)pg; //#2
  6. Superb*p3 = (Magnificent*)pm; //#3
  • 类型转换1是安全的,它将Magnificent类型的指针指向类型为Magnificent的对象
  • 类型转换2是不安全的,因为它将基类对象Grand的地址赋给派生类Magnificent的指针
  • 类型转换3是安全的,它将派生类对象的地址赋给基类指针

C++ Primer Plus 第十五章原书摘录:

dynamic_cast<Type *> (pt)

通常如果指向的对象(*pt)的类型为Type或者直接或从Type直接或间接派生而来的类型,则可以将指针pt转换为Type类型的指针;否则返回0,即空指针。也就是说只能向上转换,派生类指针可以退化成基类指针(类似脱衣服),反之不可以。

 typeid运算符和type_info类

typeid运算符是的能够确定两个对象是否为同种类型。它与sizeof有点像,可以接受两种参数:

  • 类名
  • 结果为对象的表达式

typeid运算符返回一个对type_info对象的引用,type_info是在头文件typeinfo(以前是在typeinfo.h)中定义的一个类。type_info重载了 ==和!= 运算符,以便使用这些运算符进行类型的比较:

typeid(Magnificent) == typeid(*pg)

 如果pg为空指针,程序将引发bad_typeid异常。该异常类型由exception类派生而来,在typeinfo头文件中声明的。

typeinfo类中有一个name()成员,它返回类的名称

  1. Grand *pgg = new Grand;
  2. Grand *pss = new Superb;
  3. Grand *pmm = new Magnificent;
  4. cout << typeid(*pgg).name() << endl;
  5. cout << typeid(*pss).name() << endl;
  6. cout << typeid(*pmm).name() << endl;
  7. /*
  8. * class Grand
  9. class Superb
  10. class Magnificent
  11. */
  1. #include <iostream>
  2. #include<cstdlib>
  3. #include<ctime>
  4. using namespace std;
  5. class Grand
  6. {
  7. private:
  8. int hold;
  9. public:
  10. Grand(int h = 0) :hold(h) {}
  11. virtual void Speak() const {
  12. cout << "I am a grand class!" << endl;
  13. }
  14. virtual int Value() const {
  15. return hold;
  16. }
  17. };
  18. class Superb :public Grand
  19. {
  20. public:
  21. Superb(int h = 0) :Grand(h) {}
  22. //虽然没有声明为virtual,但默认为virtual
  23. void Speak() const {
  24. cout << "I am a superb class!" << endl;
  25. }
  26. virtual void Say() const {
  27. cout << "I hold the superb value of "<<Value() << endl;
  28. }
  29. };
  30. class Magnificent :public Superb
  31. {
  32. private:
  33. char ch;
  34. public:
  35. Magnificent(int h = 0, char c = 'A') :Superb(h), ch(c) {}
  36. //两个都是虚函数
  37. void Speak() const {
  38. cout << "I am a magnificent class!" << endl;
  39. }
  40. void Say() const {
  41. cout << "I hold the character "<<ch<<" and the integer "<<Value() << endl;
  42. }
  43. };
  44. //随机产生一种对象
  45. Grand * GetOne() {
  46. Grand *p;
  47. switch (rand()%3)
  48. {
  49. case 0: p = new Grand(rand() % 100); return p;;
  50. case 1: p = new Superb(rand() % 100); return p;;
  51. case 2: p = new Magnificent(rand() % 100,'A'+ rand() % 26); return p;
  52. }
  53. }
  54. int main(void) {
  55. srand(time(0));
  56. Grand *pg;
  57. Superb *ps;
  58. for (int i = 0; i < 5; i++) {
  59. pg = GetOne();
  60. cout << "Now processing type "<<typeid(*pg).name() << endl;
  61. pg->Speak();
  62. if (ps = dynamic_cast<Superb *>(pg))
  63. ps->Say();
  64. if (typeid(Magnificent) == typeid(*pg))
  65. {
  66. cout << "Yes, you are really magnificent" << endl;
  67. }
  68. }
  69. /*
  70. I am a grand class!
  71. Now processing type class Grand
  72. I am a grand class!
  73. Now processing type class Grand
  74. I am a grand class!
  75. Now processing type class Magnificent
  76. I am a magnificent class!
  77. I hold the character Y and the integer 41
  78. Yes, you are really magnificent
  79. Now processing type class Grand
  80. I am a grand class!
  81. */
  82. return 0;
  83. }

误用RTTI的例子

"dynamic_cast" 用于在类层次结构中漫游,对指针或引用进行自由的向上、向下或交叉强制。"typeid" 则用于获取一个对象或引用的确切类型,与 "dynamic_cast" 不同,将 "typeid" 作用于指针通常是一个错误,要得到一个指针指向之对象的type_info,应当先将其解引用(例如:"typeid(*p);")。

一般地讲,能用虚函数解决的问题就不要用 "dynamic_cast",能够用 "dynamic_cast" 解决的就不要用 "typeid"。比如:

  1. void rotate(IN const CShape& iS)
  2. {
  3. if (typeid(iS) == typeid(CCircle))
  4. {
  5. // ...
  6. }
  7. else if (typeid(iS) == typeid(CTriangle))
  8. {
  9. // ...
  10. }
  11. else if (typeid(iS) == typeid(CSqucre))
  12. {
  13. // ...
  14. }
  15. // ...
  16. }

虚函数是C++众多运行时多态特性中开销最小,也最常用的机制。应当注意在对性能有苛刻要求的场合,或者需要频繁调用,对性能影响较大的地方(比如每秒钟要调用成千上万次,而自身内容又很简单的事件处理函数)要慎用虚函数

需要特别说明的一点是:虚函数的调用开销与通过函数指针的间接函数调用(例如:经典C程序中常见的,通过指向结构中的一个函数指针成员调用;以及调用DLL/SO中的函数等常见情况)是相当的。比起函数调用本身的开销(保存现场->传递参数->传递返回值->恢复现场)来说,一次指针间接引用是微不足道的。这就使得在绝大部分可以使用函数的场合中都能够负担得起虚方法的些微额外开销。

参考文章:

RTTI、虚函数和虚基类的实现方式、开销分析及使用指导

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/467140
推荐阅读
相关标签
  

闽ICP备14008679号