C++运行时类型识别

简介

运行时类型识别是C++提供的一种机制,允许程序在运行时获取对象的实际类型信息,而非仅依赖编译时的静态类型。这一特性主要解决多态场景下,基类指针/引用指向派生类对象时,无法直接确定其真实类型的问题(如Base * b = new Derived;中,b的声明类型是Base,但实际指向Derived对象

typeid的介绍

typeid是C++的运行时类型识别的关键字,用于获取对象或表达式的类型信息,返回一个const std::type_info&对象,它主要用于多态场景中确定对象的实际类型

  • 基本语法:typeid(表达式)或typeid(类型名),在使用时需要调用头文件<typeinfo>

  • 返回值:const std::type_info&,在不同编译环境下的输出内容可能会有所不同

typeid的用法多样,在visual stdio中可以根据编译器建议发现typeid的多种用法形式

在这里我们仅介绍使用频率最高的用法

 #include <typeinfo>
 #include <iostream>
 ​
 using namespace std;
 ​
 int main()
 {
     int a = 19;
     //不同编译器输出结果可能不同
     cout << typeid(a).name() << endl;
     cout << typeid(int).name() << endl;
 }

visual studio code输出结果

visual studio输出结果

可以看出在不同编译环境下的输出格式并不相同,后续为了能够直观的感受程序输出因此都展示visual studio的输出结果

核心作用

  • 识别多态对象的实际类型:对于基类指针/引用指向派生类对象的场景,typeid会返回的类型(需要确保基类又虚函数)

  • 比较类型是否相同:type_info重载了操作符==!=通过==!=比较两个type_info对象是否相等,不等,函数name()返回类型值名称

 #include <typeinfo>
 #include <iostream>
 ​
 using namespace std;
 ​
 //基类
 class Base
 {
     virtual void a()
     {
 ​
     }
 };
 ​
 class Derived : public Base
 {
 ​
 };
 ​
 int main()
 {
     Base* b = new Derived();
     cout << typeid(*b).name() << endl;
     delete b;
     return 0;
 ​
 }
 ​

输出结果

结合上述一系列操作,我们还可以结合指针实现一些复杂操作

 #include <typeinfo>
 #include <iostream>
 ​
 using namespace std;
 ​
 class A
 {
 public:
     virtual void func()
     {
         cout << "A" << endl;
     }
 };
 ​
 class B : public A
 {
 public:
     void func()
     {
         cout << "B" << endl;
     }
 };
 ​
 void test(A* pa)
 {
     //通过typeid做类型检查
     if (typeid(*pa).name() == typeid(B).name())
     {
         pa->func();
     }
     
 }
 ​
 int main()
 {
     B * pb;
     
     pa = &b;
     pa->func();
 ​
     //传入对象为B的实例化成员
     test(pa);
     //输出指针的类型
     cout << typeid(pa).name() << endl;
     //输出指针指向的对象类型
     cout << typeid(*pa).name() << endl;
     return 0;
 ​
 }

输出结果

上述代码中我们实现了通过指针来输出指针类型和指针指向类型,也实现了通过类成员的类型作为条件判断调用不同类内的函数

转型操作

向上转型操作在之前就提到过,这也是C++中的一大特征呢,允许派生类直接给基类赋值,我们在操作时其实也就相当于在隐式调用static_cast关键字

static_cast关键字介绍

基本语法
 //基本数据类型转换
 目标类型 变量 = static_cast<目标类型>(源表达式);
 //向上转型
 基类指针/引用 = static_cast<基类类型*>(派生类指针引用);
 //向下转型
 派生类指针/引用 = static_cast<派生类类型*>(基类指针/引用);

static_cast是C++中最常用的静态类型转换关键字,用于在编译期完成类型转换,适用于多种场景

在这里我们仅介绍与C++转型操作有关的使用场景

向上转型
 class Base
 {
     ......;
 };
 class Derived : public Base {};
 ​
 Derived d;
 //派生类->基类
 Base * pb = static_cast<Base*>(&d);

当然static_cast关键字还能实现向下转型操作,但是我们并不提倡这种做法,因为static_cast关键字并不能提供类型检查,所以使用static_cast实现向下转型是一种很危险的操作

 #include <typeinfo>
 #include <iostream>
 ​
 using namespace std;
 ​
 class A
 {
     public:
     virtual void func()
     {
         cout << "A" << endl;
     }
 };
 ​
 class B : public A
 {
     public:
     void func()
     {
         cout << "B" << endl;
     }
 };
 ​
 ​
 int main()
 {
     A a;
     B b;
 ​
     A * pa = &a;
     B * pb = &b;
 ​
     //向上转型操作
     a = static_cast<A>(b);
     pa = static_cast<A*>(pb);
 ​
     //向下转型操作
     //引发报错:基类对象不能直接赋值给派生类对象
     b = a;
     //引发报错:基类对象不能直接赋值给派生类指针
     pb = pa;
     //引发报错:不能直接将类对象转化为派生类对象
     b = static_cast<B>(a);
     //不会引发报错,但是存在潜在危险,不能忽视
     pb = static_cast<B*>(pa);
 ​
     return 0;
 }

dynamic_cast关键字介绍

操作限制

dynamic_cast关键字就能够实现向下转型的操作,但是在操作时也会受到限制

  • dynamic_cast仅支持两种转换

    1. 基类指针->派生类指针

    2. 基类引用->派生类引用

  • 仅适用于多态类型,即基类必须要包含虚函数

对于上一部分代码中的第45行语句,只要将其中的static_cast关键字转换为dynamic_cast即可完成转换

语法形式:
 //基础语法模板
 dynamic_cast<目标类型>(源对象)
 //指针类型转换
 目标类型指针 = dynamic_cast<目标类型*>(源指针);
 //引用类型转换
 目标类型引用 = dynamic_cast<目标类型&>(源引用);

失败返回值:
  • 若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针

  • 若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用

类型检查
 void test(A * pa)
 {
     //判断传入对象是否为B类型
     B *pb = dynamic_cast<B *>(pa);
 ​
     //如果不等于空指针,则表明转换成功
     if(pb != nullptr)
     { 
         pb->func();
     }
 }

对比

特性 static_cast dynamic_cast
检查时机 编译期(无运行时检查) 运行时(通过RTTI机制检查类型兼容性)
向下转型风险 可能产生不安全的转换(如基类指针转派生类) 若转换不安全,返回nullptr(指针)或抛异常(引用
类型要求 无需多态(类无需虚函数) 必须多态(基类至少包含一个虚函数)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐