• C++_新特性2-RTTI运行阶段类型识别


    这部分属于C++的新特性,感觉比较高阶的特性。我把它归于属于奇技淫巧的范畴。了解即可。

    RTTI运行阶段类型识别(Runtime Type Identification)的简称。

    这是添加到C++中的新特性。

    很多老式的编译器不支持它,或者可能包含开关RTTI的编译器设置。

    RTTI旨在位程序在运行阶段确定对象的类型提供一种标准方式

    很多类库已经为其对象提供了实现这种功能的方式,但是由于C++内部不支持,因此各个厂商的机制通常互不兼容。

    创建一种RTTI语言标准将使得未来的库能够彼此兼容

    一、RTTI的用途

        假设有一个类层次结构,其中的类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。

    这样便可以调用这样的函数:在处理一些信息后,选择一个类,并创建这种类型的对象,然后返回它的地址,而该地址可以被赋值给基类的指针。

    但是如何知道指针指向的是哪种对象呢

        在回答这个问题之前,首先要考虑为何要知道对象类型。可能希望调用类方法的正确版本,在这种情况下,只要该函数是类层次结构中所有成员都拥有的虚函数,并不真正需要知道对象的类型。

    但派生对象可能包含而不是继承而来的方法,在这种情况下,只有某些类型的对象可以使用该方法。也可能是出于调试目的,想跟踪生成的对象的类型。

    二、RTTI的工作原理

    C++有3个支持RTTI的元素

      dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针。否则,该运算符返回0-空指针。

      typeid运算符返回一个而支出对象的类型的值。

      type_info结构存储了有关特定类型的信息。

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

    RTTI只适用于包含虚函数的类。

    下面详细介绍RTTI的这3个元素:

    1、dynamic_cast运算符

         这个运算符不能够回答“指针指向的是哪类对象”这样的问题。但是能够回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题。我们来看一下这意味着什么?

    假设有下面这样的类层次结构:

      class Grand { }

      class Superb { }

      class Magnificent { }

    接下来假设有下面的指针:
    Grand * pg = new Grand;

    Grand * ps = new Superb;

    Grand * pm = new Magnificent;

    最后,对于下面的类型转换:

    Magnificent * p1 = (Mginificent *) pm;      //#1

    Magnificent * p2 = (Magnificent *) pg;      //#2

    Superb * p3 = (Magnificent *) pm;           //#3

    #1 是安全的,因为对象是Magnificent类型,而指针也是Magnificent派生类类型;

    #2 是不安全的,因为对象是Grand基类类型,而指针是Magnificent派生类类型;

    #3 是安全的,因为它将派生类Magnificent对象赋值给直接基类Superb类型的指针;

    对于派生类对象Magnifcent将其地址赋值给三种类型的指针都是安全的。

    虚函数确保了这3种指针中的任何一种指向Magnificent对象时,都将调用Magnificent方法。

    这里要注意一下,与问题“指针指向的是哪种类型的对象”相比,问题“类型转换是否安全”更加通用,也更有用。

    通常想知道类型的原因在于:知道类型后,就可以知道调用特定的方法是否安全。

    要调用方法,类型不一定要完全匹配,而可以是定义了方法的虚拟版本的基类类型。

    接下来看一个使用dynamic_cast例子:

    Superb * pm = dynamic_cast<Superb *>(pg);

    其中pg指向一个对象,指针pg的类型是否可被安全地转换为Superb *?如果可以,运算符将返回对象的地址,否则,返回一个空指针。

      如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型, 则下面的表达式将指针pt转换为Type类型的指针:

        dynamic_cast<Type *>(pt)

      否则,结果为0,返回空指针。 

     1 //rtti1.cpp  -- using the RTTI dyanmic_cast operator
     2 #include <iostream>
     3 #include <cstdlib>
     4 #include <ctime>
     5 
     6 using std::cout;
     7 class Grand
     8 {
     9 private:
    10     int hold;
    11 public:
    12     Grand(int h =0): hold(h) {}
    13     virtual void Speak() const {cout<<"I am a grand class!
    ";}
    14     virtual int Value() const {return hold;}
    15 };
    16 
    17 class Superb : public Grand
    18 {
    19 public:
    20     Superb(int h =0):Grand(h) {}
    21     void Speak() const {cout<<"I am a superb class!
    ";}
    22     virtual void Say() const
    23         {cout<<"I hod the superb value of"<<Value()<<"!
    ";}    
    24 };
    25 
    26 class Magnificent : public Superb
    27 {
    28 private:
    29     char ch;
    30 public:
    31     Magnificent(int h = 0, char c = 'A'):Superb(h),ch(c) {}
    32     void Speak() const {cout<<"I am a magnificent class!!!
    ";}
    33     void Say() const {cout<<" I hold the character "<<ch<<" and the integer"<<Value()<<"!
    ";}  
    34 };
    35 
    36 Grand * GetOne();
    37 
    38 int main()
    39 {
    40     std::srand(std::time(0));
    41     Grand * pg;
    42     Superb * ps;
    43     for (int i = 0; i<5; i++)
    44     {
    45         pg = GetOne();
    46         pg->Speak();
    47         if (ps = dynamic_cast<Superb *>(pg))
    48             ps->Say();
    49     }
    50     return 0;
    51 }
    52 
    53 Grand * GetOne()
    54 {
    55     Grand * p;
    56     switch(std::rand() %3)
    57     {
    58         case 0: p = new Grand(std::rand() % 100);
    59                     break;
    60         case 1: p = new Superb(std::rand() % 100);
    61                     break;
    62         case 2: p = new Magnificent(std::rand() % 100, 'A'+std::rand()%26);
    63                     break;
    64     }
    65     return p;
    66 }
    67     

    程序说明

    顶一个一个GetOne()函数,该函数随机创建这3种类中某种类的对象,并对其进行初始化;然后将地址作为Grand * 指针返回。

    循环将该指针赋给Grand * 变量 pg,然后用pg调用Speak()函数。因为这个函数是虚拟的,所以代码能够正确地调用指向的对象的Speak()版本。

    但是不能用相同的方式来调用Say()函数,因为Grand类没有定义它。但是可以使用dynamic_cast运算符来检查是否可以将pg的类型安全转换为Superb指针。

    如果对象的类型为Superb或Magnificent的话,则可以安全转换。这样就可以安全地调用Say()函数。

    2、typeid运算符和type_info类

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

    类名;

    结果为对象的表达式;

    typeid运算符返回一个队type_info对象的引用,其中,type_info是在头文件typeinfo中定义的一个类。

    type_info类重载了 == 和 != 运算符,以便可以使用这些运算符来对类型进行比较。

    例如,如果pg指向的是一个Magnificent对象。则下述表达式的结果为true或者为false。

      typeid(Magnificent) == typeif(*pg)

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

     

    type_info类的实现随厂商而异,但包含一个name()成员,该函数返回一个随实现而异的字符串,通常但并非一定是类的名称。

     

    接下来程序对上段程序做了一些修改,以使用typeid运算符和name()成员的函数。注意,它们都适用于dynamic_cast和virtual函数不能处理的情况。

    typeid测试用来选择一种操作,因为操作不是类的方法,所以不能通过类指针调用它。name()方法语句演示了如何将方法用于调试。

     1 //rtti2.cpp -- using dynamic_cast, typeid, and type_info
     2 #include <iostream>
     3 #include <cstdlib>
     4 #include <ctime>
     5 #include <typeinfo>
     6 using namespace std;
     7 
     8 class Grand
     9 {
    10 private:
    11     int hold;
    12 public:
    13     Grand(int h = 0):hold(h) {}
    14     virtual void Speak() const {cout<<"I am a grand class!
    ";}
    15     virtual int Value() const {return hold;}
    16 };
    17 
    18 
    19 class Superb:public Grand
    20 {
    21 public:
    22     Superb(int h = 0):Grand(h)  {}
    23     void Speak() const {cout<<"I am a superb class!!
    ";}
    24     virtual void Say() const
    25         {cout<<"I hold the superb value of "<<Value()<<"!
    ";}
    26 };
    27 
    28 
    29 class Magnificent : public Superb
    30 {
    31 private:
    32     char ch;
    33 public:
    34     Magnificent(int h =0, char cv = 'A'):Superb(h),ch(cv) {}
    35     void Speak() const {cout << "I am a magnificent class!!
    "}
    36     virtual void Say() const
    37         {coust << "I hold ther character "<<ch<<"and the integer"<<Value()<<"!
    ";}
    38 };
    39 
    40 Grand * GetOne();
    41 int main()
    42 {
    43     srand(time(0));
    44     Grand * pg;
    45     Superb * ps;
    46     for(int i = 0; i<5; i++)
    47     {
    48         pg = GetOne();
    49         cout<<"Now processing type "<<typeid(*pg).name()<<".
    ";
    50         pg ->Speak();
    51         if (ps = dynamic_cast<Superb *>(pg))
    52             ps ->Say();
    53         if(typeid(Magnificent)==typeid(*pg))
    54             cout<<"Yes, you're really magnificent.
    ";
    55     }
    56     return 0;
    57 }
    58 
    59 Grand * GetOne()
    60 {
    61     Grand * p;
    62     switch (rand() % 3)
    63     {
    64         case 0 :  p = new Grand(rand() %100);
    65                             break;
    66         case 1 :  p = new Superb(rand() %100);
    67                             break;
    68         case 2 :  p = new Magnificent(rand() %100, 'A'+rand())%26);
    69                             break;
    70     }
    71     return p;
    72 }

    3、误用RTTI的例子

       接下来讨论,对RTTI应避免的编程方式:

    Grand * pg;

    Superb * ps;

    for(int i =0; i<5; i++)

    {

      pg = GetOne(0;

      pg->Speak();

      if(ps = dynamic_cast<Superb *>(pg))

        ps->Say();

    }

    通过放弃dynamic_cast和虚函数,而使用typeid,可以将上述代码重新编写为:

    Grand * pg;

    Superb * ps;

    Magnificent * pm;

    for(int i = 0; i<5; i++)

    {

        pg = Getone();

        if ( typeid(Magnificent) == typeid(*pg))

        {

            pm = (Magnificent *) pg;

            pm -> Speak();

            pm ->Say();

        }

        else if (typeid(Superb) == typeid(*pg))

        {

            ps = (Superb *)pg;

            ps -> Speak();

            ps -> Say();

        }

        else

            pg->Speak();

    }

    上述代码不仅比原来的更难看,更长,而且显式地指定各个类存在严重的缺陷。

    例如,假设你发现必须从Magnificent类派生出一个Insufferable类,而后者需要重新定义Speak()和Say()。

    用typeid来显式地测试每种类型时,就需要修改for循环的代码,添加一个else if,但无需修改原来的版本。

    下面的语句适合所有从Grand派生而来的类:

    pg -> Speak();

    而下面的语句适用于所有从Superb派生而来的类:

    if (ps = dynamic_cast<Superb *>(pg))

      ps ->Say();

    如果发现在扩展的if else语句系列中使用了typeid,则应考虑是否应该使用虚函数和dynamic_cast。

  • 相关阅读:
    django orm查询和后端缓存的使用
    QuerySet的常用方法
    使用wsgiref手撸web框架
    requests小技巧
    rabbitmq简单使用
    redis简单使用
    pycharm中mongodb可视化插件
    datatime汇总
    python编码和数据转化问题汇总
    mongoengine简单使用
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10422207.html
Copyright © 2020-2023  润新知