当前位置: 首页 > article >正文

C++ RTTI

类型识别RTTI(运行时)

有什么用

RTTI 运行阶段类型识别,Runtime Type Identification 的简称。旨在为程序在运行阶段确定对象的类型提供一种标准方式。

很多类库为其类对象提供了实现RTTI这种功能的方式,但C++内部不支持。一些老式实现不支持RTTI。

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

用途(为什么要RTTI)

  • RTTI可以帮我们知道,指针指向的具体是哪个派生类对象(或者是基类对象)。

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

  • 虽然可能是类层次结构中所有成员都拥有同一个的虚函数,调用时并不需要关心确切对象的类型

    但,如果希望确保调用类方法的正确版本。RTTI为以下两种情况提供解决方案。

    • 派生类可能包含不是继承而来的方法。只有某些类型的对象,才可以使用该方法。

    • 出于调试的目的,想跟踪生成的对象的类型。

在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid

  • dynamic_cast:允许运行时进行类型转换,使程序能够在一个类层次结构中安全地转化类型(只允许向上转换)

  • typeid:返回一个指出对象的类型的值

    • C++的关键字之一,等同于sizeof这类的操作符。

    • 返回结果是名为type_info的标准库类型的对象的引用

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

只适用于含虚函数的类

适用场景:只能将RTTI用于包含虚函数的类层次结构,否则是不准的~

#include <typeinfo>
// 不包含虚函数, 错误使用
struct A {};
struct B : A {};
A * a = new B;
if (typeid(A) == typeid(*a)) {} // true 错误

// 包含虚函数, 正确使用
struct A {
    virtual ~A() = default;
};
struct B : A {};
A * a = new B;
if (typeid(A) == typeid(*a)) {} // false 正确

操作符

类型安全转换 - dynamic_cast

dynamic_cast运算符 是最常用的RTTI组件。

用途:

  • 不能回答,指针指向的是哪类对象。

  • 用于解决,是否可以安全地将对象赋给特定类型的指针

    相比 “指针指向的是哪种类型的对象” 问题 “类型转换是否安全” 更通用、更有用。

    • 要调用方法,类型不一定要完全匹配,而可以是定义了方法的虚拟版本的基类类型。
    • 知道类型后,只需要知道调用特地的方法是否安全就足够了。

什么是安全的?

  • 派生类对象的地址,赋给基类的指针。一定是安全的。

  • 基类对象的地址,赋给派生类的指针。不一定是安全的,可能安全、也可能不安全。

/* 类定义 */
class Grand {...}; // 包含虚方法
class Superb : public Grand {...};
class Magnificent : public Superb {...};
/* 对象 */
Grand  * pg = new Grand;
Superb * ps = new Superb;
Magnificent * pm = new Magnificent;
/* 类型转换 */
Magnificent * p1 = (Magnificent *) pm; // ok
Magnificent * p2 = (Magnificent *) pg; // 不一定ok
Grand * p3       = (Grand *) pm; // ok

语法

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

    指针pg能否安全的转换为Superb *

    • 安全:返回对象的地址。

    • 不安全:返回0,即 空指针。

    // 判断是否能安全的转换:
    /* 不能安全转换,则ps=0 空指针,不执行成员函数Say() */ 
    if (ps = dynamic_cast<Superb *>(pg)) 
        ps->Say();
    
  • Super & pm = dynamic_cast<Superb &>(pg); 也可以用于引用

    • 安全:返回对象的地址。

    • 不安全:

      • bad_cast类型的异常,表示请求不正确。

        bad_castexception类派生而来,在头文件typeinfo中定义。

      • 因为0表示空指针,但是没有与空指针对应的引用值。因此无法使用特殊的引用值表示失败。

      #include <typeinfo> // for bad_cast
      
      try{
          
      }
      catch (bad_cast &){
          ...
      };
      

typeid 运算符

基本介绍

typeid运算符:C++的关键字之一,等同于sizeof这类的操作符。在头文件typeinfo中定义。

  • 头文件

    // cpp  
    #include <typeinfo>
    // c
    #include <typeinfo.h>
    
  • 返回值

    • 一个对type_info对象的引用,可用于获得关于对象的信息。
    • bad_info类的实现,随厂商而异,但都包含一个name()成员。
  • 用途

    • 确定两个对象是否为同种类型。

      • type_info类重载了==!=运算符

      • 可以使用这些运算符来对类型进行比较。

      • 对两个typeid的返回值进行比较,以确定对象是否为特定的类型,

    • 运行时获知变量类型名称。

      • 使用 type_info对象 的name()方法
      • name()方法,可以方便的将方法用于调试。
      • name()函数返回一个随实现而异的字符串,通常(不一定)是类的名称。
  • 空指针异常

    • bad_typeid异常类型从exception类派生而来,在头文件typeinfo中声明。
#include <typeinfo>
#include <typeinfo.h> // 或c语言风格

int ia = 3;
// 需要注意不是所有编译器都输出”int”、”float”等之类的名称,对于这类的编译器可以这样使用
if(typeid(ia) == typeid(int))
{
    cout <<"int" <<endl;
    // 运行时获知变量类型名称  typeid(变量).name()
    cout << typeid(ia).name() << endl;
}


// 空指针异常
Magnificent * pg = new Magnificent;
typeid(Magnificent) = typeid(*pg); // pg是空指针,则程序引发bad_typeid异常。
示例

typeid测试用来针对具体的类,选择操作。

// rtti2.cpp  -- using dynamic_cast, typeid, and type_info
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <typeinfo>
using namespace std;

class Grand
{
private:
    int hold;
public:
    Grand(int h = 0) : hold(h) {}
    virtual void Speak() const { cout << "I am a grand class!\n";}
    virtual int Value() const { return hold; }
};

class Superb : public Grand
{
public:
    Superb(int h = 0) : Grand(h) {}
    void Speak() const {cout << "I am a superb class!!\n"; }
    virtual void Say() const
        { cout << "I hold the superb value of " << Value() << "!\n";}
};

class Magnificent : public Superb
{
private:
    char ch;
public:
    Magnificent(int h = 0, char cv = 'A') : Superb(h), ch(cv) {}
    void Speak() const {cout << "I am a magnificent class!!!\n";}
    void Say() const {cout << "I hold the character " << ch <<
               " and the integer "  << Value() << "!\n"; }
};

Grand * GetOne();
int main()
{
    srand(time(0));
    Grand * pg;
    Superb * ps;
    for (int i = 0; i < 5; i++)
    {
        pg = GetOne();
        cout << "Now processing type " << typeid(*pg).name() << ".\n";
        pg->Speak();
        if( ps = dynamic_cast<Superb *>(pg))
            ps->Say();
        if (typeid(Magnificent) == typeid(*pg))
            cout << "Yes, you're really magnificent.\n";
    }
    // std::cin.get();
    return 0;
}

Grand * GetOne()
{
    Grand * p;

    switch( rand() % 3)
    {
        case 0: p = new Grand(rand() % 100);
                    break;
        case 1: p = new Superb(rand() % 100);
                    break;
        case 2: p = new Magnificent(rand() % 100, 'A' + rand() % 26);
                    break;
    }
    return p; 
}

尽量避免使用typeid

尽量使用dynamic_cast和虚函数,而不推荐使用typeid运算符进行逐一判断,因为:

  • 需要显式地指定各个具体的类,代码更长更难看。
  • 存在只适用于含虚函数的类的缺陷
/* 使用dynamic_cast */
Grand * pg;
Super * ps;
for (int i = 0; i < 5; i++)
{
    pg = GetOne();
    pg->Speak();
    if (ps = dynamic_cast<Superb *>(pg) )
    	pg->Say();
}

如果使用typeid显式地测试每个类型时,必须修改for循环的代码。

即:添加else if,但无需修改原来的版本。

Grand * pg;
Super * ps;
Magnificent * pm;
for (int i = 0; i < 5; i++)
{
    pg = GetOne();
    if (typeid(Magnificent) == typeid(*pg))
    {
        pm = (Magnificent *) pg;
        pm->Speak();
        pm->Say();
    }
    if (typeid(Superb) == typeid(*pg))
    {
        pm = (Superb *) pg;
        pm->Speak();
        pm->Say();
    }
    else
        pg->Speak();
}

http://www.kler.cn/a/534419.html

相关文章:

  • 洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解
  • 如何利用i18n实现国际化
  • 吴签磁力_简单多功能的磁力搜索工具
  • 【含文档+PPT+源码】基于Python爬虫二手房价格预测与可视化系统的设计与实现
  • uniapp小程序自定义中间凸起样式底部tabbar
  • (dpdk f-stack)-堆栈溢出-野指针-内存泄露(问题定位)
  • 如何利用i18n实现国际化
  • nginx日志查询top10
  • 代码随想录算法训练营打卡第56天
  • leetcode:LCR 179. 查找总价格为目标值的两个商品(python3解法)
  • ES6 const 使用总结
  • 美团-测试开发面试
  • DeepSeek推理模型架构以及DeepSeek爆火的原因
  • Vue 3 30天精进之旅:Day 15 - 插件和指令
  • 【spring容器管理】bean的生命周期有哪些拓展点?
  • 个人毕业设计--基于HarmonyOS的旅行助手APP的设计与实现(挖坑)
  • Java程序员 面试如何介绍项目经验?
  • 一表总结 Java 的3种设计模式与6大设计原则
  • 蓝桥杯翻转
  • 【100%通过率 】【华为OD机试c++/java/python】日志采集系统【 E卷 | 2023 Q1 |100分】
  • Linux特权组全解析:识别GID带来的权限提升风险
  • C++初阶 -- vector容器的接口详解
  • 机器学习--python基础库之Matplotlib (1) 超级详细!!!
  • 现场流不稳定,EasyCVR视频融合平台如何解决RTSP拉流不能播放的问题?
  • Python|Pyppeteer实现链接并打开比特浏览器进行自动化操作(31)
  • 说一下Kafka你是怎么进行配置的?例如生产者配置和消费者配置