(转)C++的 RTTI 概念和用途

自从1993年Bjarne Stroustrup 〔注1 〕提出有关C++ 的RTTI功能之建议﹐以及C++的异常处理(exception handling)需要RTTI;最近新推出的C++ 或多或少已提供RTTI。 然而,若不小心使用RTTI,可能会导致软件弹性的降低。本文将介绍RTTI的观念和近况﹐并说明如何善用它。

 

什么是RTTI?
     在C++ 环境中﹐头文件(header file) 含有类之定义(class definition)亦即包含有关类的结构资料(representational information)。但是﹐这些资料只供编译器(compiler)使用﹐编译完毕后并未留下来﹐所以在执行时期(at run-time) ﹐无法得知对象的类资料﹐包括类名称、数据成员名称与类型、函数名称与类型等等。例如﹐两个类Figure和Circle﹐其之间为继承关系。 
若有如下指令﹕
Figure *p;
p = new Circle();
Figure &q = *p;
    在执行时﹐p指向一个对象﹐但欲得知此对象之类资料﹐就有困难了。 同样欲得知q 所引用对象的类资料﹐也无法得到。RTTI(Run-Time Type Identification)就是要解决这困难﹐也就是在执行时﹐您想知道指针所指到或引用到的对象类型时﹐该对象有能力来告诉您。随着应用场合之不同 ﹐所需支持的RTTI范围也不同。

最单纯的RTTI包括﹕ 
类识别(class identification)──包括类名称或ID。
继承关系(inheritance relationship)──支持执行时期的「往下变换类型」(downward casting)﹐亦即动态变换类型(dynamic casting)

    在对象数据库存取上﹐还需要下述RTTI﹕ 
对象结构(object layout) ──包括属性的类型、名称及其位置(position或offset)。
成员函数表(table of functions)──包括函数的类型、名称、及其参数类型等。
    其目的是协助对象的I/O 和持久化(persistence) ﹐也提供调试讯息等。 
     若依照Bjarne Stroustrup 之建议〔注1 〕﹐C++ 还应包括更完整的RTTI﹕ 
●能得知类所实例化的各对象 。
●能参考到函数的源代码。
●能取得类的有关在线说明(on-line documentation) 。
    其实这些都是C++ 编译完成时所丢弃的资料﹐如今只是希望寻找个途径来将之保留到执行期间。然而﹐要提供完整的RTTI﹐将会大幅提高C++ 的复杂度﹗

RTTI可能伴随的副作用
      RTTI最主要的副作用是﹕程序员可能会利用RTTI来支持其「复选」(multiple-selection)方法﹐而不使用虚函数(virtual function)方法。 
虽然这两种方法皆能达到多态化(polymorphism) ﹐但使用复选方法﹐常导致违反著名的「开放╱封闭原则」(open/closed principle) 〔注2 〕。反之﹐使用虚函数方法则可合乎这个原则. 
      Circle和Square皆是由Figure所派生出来的子类﹐它们各有自己的draw()函数。当C++ 提供了RTTI﹐就可写个函数如下﹕ 
void drawing( Figure *p )
{
    if( typeid(*p).name() == "Circle" ) 
      ((Circle*)p) -> draw(); 
    if( typeid(*p).name() == "Rectangle" ) 
      ((Rectangle*)p) -> draw(); 
}
    虽然drawing() 函数也具有多型性﹐但它与Figure类体系的结构具有紧密的相关性。当Figure类体系再派生出子类时﹐drawing() 函数的内容必须多加个if指令。因而违反了「开放╱封闭原则」﹐如下﹕ 
    很显然地﹐drawing() 函数应加以修正。 
    想一想﹐如果C++ 并未提供RTTI﹐则程序员毫无选择必须使用虚函数来支持drawing() 函数的多型性。于是程序员将draw()宣告为虚函数﹐并写drawing() 如下﹕ 
void drawing(Figure *p)
{ p->draw(); }
    如此﹐Figure类体系能随时派生类﹐而不必修正drawing() 函数。亦即﹐Figure体系有个稳定的接口(interface) ﹐drawing() 使用这接口﹐使得drawing() 函数也稳定﹐不会随Figure类体系的扩充而变动。这是封闭的一面。而这稳定的接口并未限制Figure体系的成长﹐这是开放的一面。因而合乎「开放╱ 封闭」原则﹐软件的结构会更具弹性﹐更易于随环境而不断成长。

RTTI的常见的使用场合
    一般而言﹐RTTI的常见使用场合有四﹕异常处理(exceptions handling)、动态转类型(dynamic casting) 、模块集成、以及对象I/O 。 
1.异常处理── 大家所熟悉的C++ 新功能﹕异常处理﹐其需要RTTI﹐如类名称等。
2.动态转类型── 在类体系(class hierarchy) 中﹐往下的类型转换需要类继承的RTTI。
3.模块集成── 当某个程序模块里的对象欲跟另一程序模块的对象沟通时﹐应如何得知对方的身分呢﹖知道其身分资料﹐才能呼叫其函数一 般的C++ 程序﹐常见的解决方法是──在源代码中把对方对象之类定义(即存在头文件里)包含进来﹐在编译时进行连结工作。然而﹐像目前流行的主从(Client- Server) 架构中﹐客户端(client)的模块对象﹐常需与主机端(server)的现成模块对象沟通﹐它们必须在执行时沟通﹐但又常无法一再重新编译。于是靠标 头文件来提供的类定义资料﹐无助于执行时的沟通工作﹐只得依赖RTTI了。
4.对象I/O ── C++ 程序常将其对象存入数据库﹐未来可再读取之。对象常内含其它小对象﹐因之在存入数据库时﹐除了必须知道对象所属的类名称﹐也必须知道各内含小对象之所属类 ﹐才能完整地将对象存进去。储存时﹐也将这些RTTI资料连同对象内容一起存入数据库中。未来读取对象时﹐可依据这些RTTI资料来分配内存空间给对象。

RTTI从那里来?
    上述谈到RTTI的用途﹐以及其副作用。这众多争论﹐使得RTTI的标准迟迟未呈现出来。也导致各C++ 开发环境提供者﹐依其环境所需而以各种方式来支持RTTI﹐且其支持RTTI的范围也所不同。 目前常见的支持方式包括﹕ 
●由类库提供RTTI──例如﹐Microsoft 公司的Visual C++环境。
●由C++ 编译器(compiler)提供──例如﹐Borland C++ 4.5 版本。
●由源代码产生器(code generator)提供──例如Bellvobr系统。
●由OO数据库的特殊预处理器(preprocessor)提供──例如Poet系统。
由程序员自己加上去。
    这些方法皆只提供简单的RTTI﹐其仅为Stroustrup先生所建议RTTI内涵的部分集合而已。相信不久的将来﹐会由C++ 编译器来提供ANSI标准的RTTI﹐但何时会订出这标准呢﹖ 没人晓得吧﹗

程序员自己提供的RTTI
    通常程序员自己可提供简单的RTTI﹐例如提供类的名称或识别(TypeID)。最常见的方法是﹕为类体系定义些虚函数如Type_na() 及Isa() 函数等。请先看个例子﹕ 
class Figure { };
class Rectangle : public Figure { };
class Square : public Rectangle
{

int data;
public:
Square() { data=88; }
void Display() { cout << data << endl; }
};
void main()
{

Figure *f = new Rectangle();
Square *s = (Square *)f;
s -> Display();
}
    这时s 指向Rectangle 之对象﹐而s->Display()呼叫Square::Display() ﹐将找不到data值。若在执行时能利用RTTI来检查之﹐就可发出错误讯息。于是﹐自行加入RTTI功能﹕ 
class Figure
{

public:
  virtual char* Type_na() { return "Figure"; } 
  virtual int Isa(char* cna) { return !strcmp(cna, "Figure")? 1:0; } 
};

 

class Rectangle:public Figure
{ public:
  virtual char* Type_na() { return "Rectangle"; } 
  virtual int Isa(char* cna)  { return !strcmp(cna, "Rectangle")?1 : Figure::Isa(cna); } 
  static Rectangle* Dynamic_cast(Figure* fg) { return fg -> Isa(Type_na())?(Rectangle*)fg : 0; } 
};

 

class Square:public Rectangle
{

int data;
public:
Square() { data=88; }
  virtual char* Type_na() { return "Square"; } 
  virtual int Isa(char* cna) { return !strcmp(cna, "Rectangle")? 1 : Rectangle::Isa(cna); } 
  static Square* Dynamic_cast(Figure *fg)  { return fg->Isa(Type_na())? (Square*)fg : 0; } 
  void Display() { cout << "888" << endl; } 
};
    虚函数Type_na() 提供类名称之RTTI﹐而Isa() 则提供继承之RTTI﹐用来支持「动态转类型」函数──Dynamic_cast()。例如﹕ 
Figure *f = new Rectangle();
cout << f -> Isa("Square") << endl;
cout << f -> Isa("Figure") << endl;
    这些指令可显示出﹕f 所指向之对象并非Square之对象﹐但是Figure之对象(含子孙对象)。再如﹕
Figure *f; Square *s;
f = new Rectangle();
s = Square == Dynamic_cast(f);
if(!s)
cout << "dynamic_cast error!!" << endl;
此时﹐依RTTI来判断出这转类型是不对的。

 

类库提供RTTI
    由类库提供RTTI是最常见的﹐例如Visual C++的MFC 类库内有个CRuntimeClass 类﹐ 其内含简单的RTTI。请看个程序﹕ 
class Figure:public CObject
{
DECLARE_DYNAMIC(Figure);
};
class Rectangle : public Figure
{
DECLARE_DYNAMIC(Rectangle);
};
class Square : public Rectangle
{
DECLARE_DYNAMIC(Square);
int data;
public:
void Display() { cout << data << endl; }
Square() { data=88; }
};
IMPLEMENT_DYNAMIC(Figure, CObject);
IMPLEMENT_DYNAMIC(Rectangle, Figure);
IMPLEMENT_DYNAMIC(Square, Rectangle);
    Visual C++程序依赖这些宏(Macor) 来支持RTTI。现在就看看如何使用CRuntimeClass类吧﹗如下﹕ 
CRuntimeClass *r;
Figure *f = new Rectangle();
r = f->GetRuntimeClass();
cout << r->m_psClassName << endl;
      这就在执行时期得到类的名称。Visual C++的类库仅提供些较简单的RTTI──类名称、对象大小及父类等。至于其它常用的RTTI如──数据项的类型及位置(position)等皆未提供。

 

C++编译器提供RTTI
    由C++ 语言直接提供RTTI是最方便了﹐但是因RTTI的范围随应用场合而不同﹐若C++语言提供所有的RTTI﹐将会大幅度增加C++ 的复杂度。目前﹐C++ 语言只提供简单的RTTI﹐ C++ 新增typeid()操作数以及dynamic_cast<T*>函数模版。

为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用typeid必须使用头文件<typeinfo>,因为typeid是一个返回类型为typ_info的引用的函数 

 cout<<typeid(A).name();

cout<<typeid(A).raw_name();

 

请看个程序﹕ 
class Figure
{

public:
virtual void Display();
};
class Rectangle : public Figure { };
class Square:public Rectangle
{

int data;
public:
Square() { data=88; }
void Display() { cout << data << endl; }
};
    现在看看如何使用typeid()操作数── 
Figure *f = new Square();
const type_info ty = typeid(*f);
cout << ty.name() << endl;
    这会告诉您﹕f 指针所指的对象﹐其类名称是Square。再看看如何使用dynamic_cast<T*>函数样版── 
Figure *f; Square *s;
f = new Rectangle();
s = dynamic_cast<Sqiare *>(f);
if(!s)
cout << "dynamic casting error!!" << endl;
    在执行时﹐发现f 是不能转为Square *类型的。如下指令﹕ 
Figure *f; Rectangle *r;
f = new Square();
r = dynamic_cast<Rectangle *>(f);
if(r)

r->Display();
这种类型转换是对的。
RTTI与虚函数表
    在C++ 程序中﹐若类含有虚函数﹐则该类会有个虚函数表(Virtual Function Table﹐ 简称VFT )。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐这对象内含RTTI资料
    由于该类所实例化之各对象﹐皆含有个指针指向VFT 表﹐因之各对象皆可取出typeinfo对象而得到RTTI。例如﹐ 
Figure *f1 = new Square();
Figure *f2 = new Square();
const typeinfo ty = typeid(*f2);
其中﹐typeid(*f2) 的动作是﹕
1.取得f2所指之对象。
2.从对象取出指向VMF 之指针﹐经由此指针取得VFT 表。
3.从表中找出指向typeinfo对象之指针﹐经由此指针取得typeinfo对象。
     这typeinfo对象就含有RTTI了。经由f1及f2两指针皆可取得typeinfo对象﹐所以 typeid(*f2) == typeid(*f1)。 
总结
    RTTI是C++ 的新功能。过去﹐C++ 语言来提供RTTI时﹐大多依赖类库来支持﹐但各类库使用的方法有所不同﹐使得程序的可移植性(portability) 大受影响。然而﹐目前C++ 也只提供最简单的RTTI而已﹐可预见的未来﹐当大家对RTTI的意见渐趋一致时﹐C++ 将会提供更完整的RTTI﹐包括数据项和成员函数的类型、位置(offset)等资料﹐使得C++ 程序更井然有序﹐易于维护。 
参考资料
[注1] Stroustrup B., “Run-Time Type Identification for C++”, Usenix C++ C
onference, Portland, 1993.
[注2] Meyer B.,Object-Oriented Software Construction, Prentice Hall, 1988

 转自:http://www.cnblogs.com/weiqubo/archive/2011/05/11/2043837.html

转载于:https://www.cnblogs.com/yysblog/archive/2011/11/10/2244347.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422679.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1400协议是什么和28181区别_1400张拆解案例,够你PPT拆解学习好几遍了!

本文作者&#xff1a;执生若梦简介&#xff1a;「拆P教室」小伙伴、个人公众号「君梦幻灯」指导&#xff1a;优卡优卡大大前段时间开启了一个新栏目&#xff0c;图解计划。拆解一张图片&#xff0c;向其中学习PPT制作技巧。我一直在收集音乐banner图&#xff0c;现在已经达到了…

【算法设计与分析】13 分治策略的设计思想

算法中很多方法都是可以采用分治策略进行设计与优化&#xff0c;那么什么是分治策略&#xff1f;如何使用分治策略进行算法的设计与分析&#xff1f; 文章目录1. 分治策略的基本思想1.1 二分检索的设计思想1.2 二分归并排序的设计思想1.3 Hanoi塔的递归算法2 小结1. 分治策略的…

字段 新增hive_Hive分区表 | 每日五分钟学大数据

数据分区的一般概念存在已久。其可以有多种形式&#xff0c;但是通常使用分区来水平分散压力&#xff0c;将数据从物理上转移到和使用最频繁的用户更近的地方&#xff0c;以及实现其他目的。Hive中有分区表的概念。我们可以看到分区表具有重要的性能优势&#xff0c;而且分区表…

【算法设计与分析】14 分治算法的一般描述和分析方法

本文主要描述分治算法的一般描述和分析方法。衔接上一篇文章&#xff1a;【算法设计与分析】13 分治策略的设计思想 文章目录1 分治算法的一般性描述1.1 分支算法的时间分析1.2 两类常见的递推方程与求解方法2 总结1 分治算法的一般性描述 设分治算法为&#xff1a;Divide-and…

idea_取消自动import .*

打开设置 > Editor > Code Style > Java > Scheme Default > Imports ① 将 Class count to use import with “" 改为 99 &#xff08;导入同一个包的类超过这个数值自动变为 * &#xff09; ② 将 Names count to use static import with "” 改为 …

呼呗电销机器人_为什么企业销售电销都用电销外呼智能机器人

电销行业“痛点”明显&#xff0c;成为企业发展桎梏在电销企业中&#xff0c;运营成本占据了很大的资金比例&#xff0c;呼叫中心搭建和后期维护、销售人员的薪酬、培训、学习成本等都需要大量的投入&#xff0c;导致企业发展后劲不足。挂断、拒接常有&#xff0c;谁来拯救电销…

知识管理系统Data Solution研发日记之十二 网页数据抓取Fetch,呈现Render,导出Export...

这篇文章是对第四篇文章《知识管理系统Data Solution研发日记之四 片段式数据解决方案》的补充&#xff0c;提供一套完整的解决方案。请先阅读那一篇文章来了解它的原理。 Rule Editor抓取数据 这里&#xff0c;主要的工作是设定目标地址&#xff0c;正则表达式规则。我生活在深…

【算法设计与分析】15 分治策略:芯片测试

上一篇文章学习了【算法设计与分析】14 分治算法的一般描述和分析方法 文章目录1. 芯片测试1.1 一次测试的过程1.2 如何测试一块芯片的好坏1.3 蛮力算法1.4 分治算法设计思想1.41 分治算法的正确性证明1.42 时间复杂度分析2. 总结本篇文章借助具体的例子来学习分治策略。这个例…

批量 材质 调整_游戏图形批量渲染及优化:Unity静态合批技术

作者&#xff1a;枸杞忧天(本文首发于公众号“偶尔学学Unity”&#xff0c;文章仅为作者观点&#xff0c;不代表GWB立场)最近在准备公司的技术分享&#xff0c;主题是入门批量渲染&#xff0c;想着反正也总结了&#xff0c;不如充几篇博客吧&#xff0c;也算显得没有那么半途而…

css - clearfix-清除浮动

参考&#xff1a; http://www.cnblogs.com/zllwebjs/archive/2010/03/19/1689980.html http://www.cnblogs.com/zhangyufeng523/archive/2011/05/09/2041224.html 清除浮动方法有好几种&#xff0c; 1. 用空div&#xff0c;设置css为clear:both 2. 用空br&#xff0c;设置css为…

【算法设计与分析】16 分治策略:快速排序(快速排序的时间复杂度计算)

上一篇文章学习了&#xff1a;【算法设计与分析】15 分治策略&#xff1a;芯片测试 文章目录1. 快速排序的基本思想1.2 时间复杂度的计算1.21 最坏情况时间复杂度计算1.22 最好情况时间复杂度1.23 平均时间复杂度计算2 总结1. 快速排序的基本思想 用首元素 x 作划分标准&#…

的run窗口不显示_「玩转deepin」如何安装VirtualBox增强功能使得deepin全屏显示?...

在Windows上通过Virtualbox安装deepin深度操作系统&#xff0c;安装完成后&#xff0c;为了让deepin可以在virtualbox中全屏显示&#xff0c;需要安装增强工具&#xff0c;但是不少同学点击安装增强工具后会出现无法安装的情况。在【设备】中点击【安装增强功能】时&#xff0c…

掘金浏览器插件安装图文教程

本文介绍如何安装掘金chrome插件&#xff1a; Chrome插件推荐 2018-02-27 21:53 chrome插件网近日推荐一款设计师开发人员人手必备的一款插件&#xff1a;掘金插件。有网友留言给笔者说希望整理出一套掘金插件详细的安装步骤。于是就有了今天的图文详解掘金插件安装步骤。第一…

python集群到hadoop_如何使用Hadoop流在本地Hadoop集群中运行MRJob?

我正在学习一个大数据类&#xff0c;我的一个项目是在本地建立的Hadoop集群上运行Mapper/Reducer。在我一直在为类使用Python和MRJob库。在下面是我当前用于Mapper/Reducer的Python代码。在from mrjob.job import MRJobfrom mrjob.step import MRStepimport reimport osWORD_RE…

【ThinkPHP】实例化模型的方法

欢迎大家访问我自己架的博客站点 码厩技术博客&#xff01; 1、实例化一个Model function D($name, $app) { static $_model array(); if (empty($name)) return new Model; if (empty($app)) $app C(DEFAULT_APP); if (isset($_model[$app . $name])) return $_model[$app .…

【mysql技术内幕1】mysql基础架构-一条SQL查询语句是如何执行的

文章目录1 一条SQL查询语句是如何执行的2 mysql体系结构3 InnoDB存储引擎4 总结1 一条SQL查询语句是如何执行的 ​ 通常我们使用数据库&#xff0c;都是将数据库看成一个整体&#xff0c;我们的应用与数据库完全就是通过SQL语句进行交互。大多数开发者很少去了解数据库的内部实…

session实现机制_如何理解php session运行机制

php session运行机制就是客户端将session id传入到服务器中&#xff0c;服务器再根据session id找到对应的文件并将其反序列化得到session值&#xff0c;然后保存的时候先序列化再写入今天将要分享的知识点是PHP中的会话运行机制&#xff0c;对于熟悉PHP的开发者来说并不陌生&a…

【MySQL原理解析】01. 一条SQL查询语句是如何执行的

这是【MySQL原理解析】的第一篇文章&#xff0c;MySQL我看了很多的书与教程&#xff0c;对其原理有一定的理解&#xff0c;一直想写一系列的文章来把MySQL的原理给讲清楚&#xff0c;一直没有时间写&#xff0c;今天算是个开头吧。万事开头难&#xff0c;咱们先破了这个开头&am…

【转载】MOS开关(verilog)

MOS开关 一. MOS开关 1. NMOS 源极(d)接Gnd 一般情况下&#xff0c;可认为晶体管受栅极(g)电平VG的控制(control). 1). VGH&#xff0c;源极(s)与漏极(d)接通; 2). VGL&#xff0c;源极(s)与漏极(d)断开. 源极(s)与漏极(d)接通, 则漏极(d)被下拉到Gnd. 2. PMOS 源极(d)接VDD 1)…

【Linux进程、线程、任务调度】二 fork/vfork与写时拷贝 线程的本质 托孤 进程睡眠和等待队列

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信&#xff1a; liu1126137994学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff08;已满&#xff09;&#xff1a; 780902027学习交流资源…