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_cast
从exception
类派生而来,在头文件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();
}