这部分属于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,返回空指针。

 //rtti1.cpp  -- using the RTTI dyanmic_cast operator
#include <iostream>
#include <cstdlib>
#include <ctime> using std::cout;
class Grand
{
private:
int hold;
public:
Grand(int h =): 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 =):Grand(h) {}
void Speak() const {cout<<"I am a superb class!\n";}
virtual void Say() const
{cout<<"I hod the superb value of"<<Value()<<"!\n";}
}; class Magnificent : public Superb
{
private:
char ch;
public:
Magnificent(int h = , char c = 'A'):Superb(h),ch(c) {}
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()
{
std::srand(std::time());
Grand * pg;
Superb * ps;
for (int i = ; i<; i++)
{
pg = GetOne();
pg->Speak();
if (ps = dynamic_cast<Superb *>(pg))
ps->Say();
}
return ;
} Grand * GetOne()
{
Grand * p;
switch(std::rand() %)
{
case : p = new Grand(std::rand() % );
break;
case : p = new Superb(std::rand() % );
break;
case : p = new Magnificent(std::rand() % , 'A'+std::rand()%);
break;
}
return p;
}

程序说明

顶一个一个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()方法语句演示了如何将方法用于调试。

 //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 = ):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 = ):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 =, char cv = 'A'):Superb(h),ch(cv) {}
void Speak() const {cout << "I am a magnificent class!!\n"}
virtual void Say() const
{coust << "I hold ther character "<<ch<<"and the integer"<<Value()<<"!\n";}
}; Grand * GetOne();
int main()
{
srand(time());
Grand * pg;
Superb * ps;
for(int i = ; i<; 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";
}
return ;
} Grand * GetOne()
{
Grand * p;
switch (rand() % )
{
case : p = new Grand(rand() %);
break;
case : p = new Superb(rand() %);
break;
case : p = new Magnificent(rand() %, 'A'+rand())%);
break;
}
return p;
}

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。

C++_新特性2-RTTI运行阶段类型识别的更多相关文章

  1. systemd的新特性及unit常见类型分析与awk用法示列

    简述systemd的新特性及unit常见类型分析,能够实现编译安装的如nginx\apache实现通过systemd来管理 系统引导的新特性 系统引导时实现服务启动: 按需激活进程: 系统状态快照: ...

  2. Java 8 新特性之泛型的类型推导

    1. 泛型究竟是什么? 在讨论类型推导(type inference)之前,必须回顾一下什么是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据 ...

  3. 简述systemd的新特性及unit常见类型分析、使用systemd管理编译安装的nginx

    1. systemd新特性 并行处理(同时启动)所有服务. 基于依赖关系定义的服务控制逻辑 系统状态快照 按需激活进程,只有第一次被访问时才会真正启动: 2. systemd的常见unit类型 Ser ...

  4. mysql8.0 新特性,对json类型的常用操作

    mysql8 新特性-json数据类型操作 -- 根据key(可多个)获取value SELECT JSON_EXTRACT('{"id": 14, "name" ...

  5. C++_新特性总结与未来的路

    了解C++之后,可以阅读一些高级主题和面向对象编程相关的书籍: OOP有助于开发大型的项目,并提高其可靠性: OOP方法的基本活动之一就是发明能够模拟当前情况的类.当前情况被统称为问题域. 由于实际问 ...

  6. 使用C#的新特性:可空类型

    随着C#语言最新标准的出炉,现在它也提供了对可空类型的支持.这个小变化将会在处理那些包括可选项的数据库记录时非常有用.当然在其他地方,它也是非常有用的. 简单说来,可空数据类型就是包含了所定义的数据类 ...

  7. C++_新特性1-类型转换运算符

    C++的创始人认为C语言的类型转换运算符太过于松散.他采取了更加严格的限制允许的类型转换.并添加了4个类型转换运算符. 这部分特性比较高阶,我把它归于奇技淫巧的范畴.这里简单介绍一下,以后实际有用到再 ...

  8. Java8新特性 重复注解与类型注解

    import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.anno ...

  9. C# 9.0 新特性预览 - 类型推导的 new

    C# 9.0 新特性预览 - 类型推导的 new 前言 随着 .NET 5 发布日期的日益临近,其对应的 C# 新版本已确定为 C# 9.0,其中新增加的特性(或语法糖)也已基本锁定,本系列文章将向大 ...

随机推荐

  1. JSP,PHP详细性能测试

    前几天在CU看到有人比较PHP与JSP,.net,结果表明PHP比JSP,.net性能低下很多.本人认为即使有差距,也不应该有这么大,所以认真测试一下几者间的性能差距.由于很久没用.net了,所以,暂 ...

  2. resolve或reject之后还需要return吗

    答案: 需要 今日碰到一个问题, 是我的同事发现的,如果不说的话可能一直没有注意到 这个代码 在reject 后还会执行, 但是谁也没有注意到, 但是不会报错, 因为当一个promise是resolv ...

  3. 34.UCASE() LCASE() 函数

    UCASE() 函数 UCASE 函数把字段的值转换为大写. SQL UCASE() 语法 SELECT UCASE(column_name) FROM table_name SQL UCASE() ...

  4. 15.select into

    select into SELECT INTO 语句从一个表中选取数据,然后把数据插入另一个表中. SELECT INTO 语句常用于创建表的备份复件或者用于对记录进行存档. CREATE TABLE ...

  5. Oracle数据库管理

    一.Oracle 的(资源限制)概要文件 为了控制系统资源的使用, 可以利用资源限制概要文件. 资源限制概要文件是 Oracle 安全策略的重要组成部分, 利用资源限制概要文件可以对数据库用户进行基本 ...

  6. 【原创】请不要对Boost Format使用Byte作为参数

    曾几何时我们可以肆无忌惮的对sprintf传入BYTE等类型作为参数,只要你指定的为%D即可打印出对应的数字 但是boost format不可以,当你发生类型截断,错误,异常,请尽快查看你传入的类型是 ...

  7. Java 设计模式 和七大设计原则

    创建型模式 抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创建相关或依赖对象的家族, 而不需要指定具体类. 生成器模式(Builder pattern): 使 ...

  8. (转)jQuery基础之选择器

    原文地址: http://www.cnblogs.com/webmoon/p/3169360.html 选择器是jQuery的根基,在jQuery中,对事件处理.遍历DOM和Ajax操作都依赖于选择器 ...

  9. 20169219 SEED SQL注入实验

    实验环境SEED Ubuntu镜像 环境配置 实验需要三样东西,Firefox.apache.phpBB2(镜像中已有): 1.运行Apache Server:只需运行命令sudo service a ...

  10. Git代码冲突常见解决方法

    在发布这个配置文件的时候,会发生代码冲突: error: Your local changes to the following files would be overwritten by merge ...