C++中的RTTI机制解析

时间:2022-10-06 00:47:40

RTTI

RTTI概念

RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。

RTTI机制的产生

为什么会出现RTTI这一机制,这和C++语言本身有关系。和很多其他语言一样,C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致。有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求。和Java相比,C++要想获得运行时类型信息,只能通过RTTI机制,并且C++最终生成的代码是直接与机器相关的。

我对Java的运行时类型识别不是很熟悉,所以查了一下相关资料:Java中任何一个类都可以通过反射机制来获取类的基本信息(接口、父类、方法、属性、Annotation等),而且Java中还提供了一个关键字,可以在运行时判断一个类是不是另一个类的子类或者是该类的对象,Java可以生成字节码文件,再由JVM(Java虚拟机)加载运行,字节码文件中可以含有类的信息。

typeid和dynamic_cast操作符

RTTI提供了两个非常有用的操作符:typeid和dynamic_cast。

typeid操作符,返回指针和引用所指的实际类型;

dynamic_cast操作符,将基类类型的指针或引用安全地转换为其派生类类型的指针或引用。

我们知道C++的多态性(运行时)是由虚函数实现的,对于多态性的对象,无法在程序编译阶段确定对象的类型。当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用typeid必须使用头文件<typeinfo>,因为typeid是一个返回类型为typ_info的引用的函数所以这里有必要先介绍一下type_info类。

下面是typeinfo的源代码:

/***
*typeinfo.h - Defines the type_info structure and exceptions used for RTTI
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Modified January 1996 by P.J. Plauger
*
*Purpose:
* Defines the type_info structure and exceptions used for
* Runtime Type Identification.
*
* [Public]
*
****/ #pragma once #ifndef _TYPEINFO_
#define _TYPEINFO_
#ifndef RC_INVOKED
#include <xstddef>
#include <string.h> // for type_info::hash_code() #pragma pack(push,_CRT_PACKING)
#pragma warning(push,3)
#pragma push_macro("new")
#undef new
#pragma warning(disable: 4275) #ifndef __cplusplus
#error This header requires a C++ compiler ...
#endif #if !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif struct __type_info_node {
void *_MemPtr;
__type_info_node* _Next;
}; extern __type_info_node __type_info_root_node; class type_info {
public:
size_t hash_code() const _THROW0()
{ // hash name() to size_t value by pseudorandomizing transform
return (_STD _Hash_seq((const unsigned char *) name(),
_CSTD strlen(name())));
} #if defined(CRTDLL) && defined(_CRTBLD)
_CRTIMP_PURE
#endif
#ifdef _M_CEE
[System::Security::SecurityCritical]
#endif
virtual ~type_info() _NOEXCEPT;
#if defined(_SYSCRT)
_CRTIMP_PURE int __CLR_OR_THIS_CALL operator==(const type_info& _Rhs) const;
_CRTIMP_PURE int __CLR_OR_THIS_CALL operator!=(const type_info& _Rhs) const;
#else
_CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& _Rhs) const;
_CRTIMP_PURE bool __CLR_OR_THIS_CALL operator!=(const type_info& _Rhs) const;
#endif
_CRTIMP_PURE bool __CLR_OR_THIS_CALL before(const type_info& _Rhs) const;
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL raw_name() const;
private:
void *_M_data;
char _M_d_name[];
#if defined(_CRTBLD) /* TRANSITION */
__CLR_OR_THIS_CALL type_info(const type_info& _Rhs);
type_info& __CLR_OR_THIS_CALL operator=(const type_info& _Rhs);
#else
public:
__CLR_OR_THIS_CALL type_info(const type_info&) = delete;
type_info& __CLR_OR_THIS_CALL operator=(const type_info&) = delete;
private:
#endif
_CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base(const type_info *,__type_info_node* __ptype_info_node);
_CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor(type_info *);
#if defined(_CRTBLD)
#if !defined(_SYSCRT)
_CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base_internal(const type_info *,__type_info_node* __ptype_info_node);
_CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor_internal(type_info *); public:
// CRT dll import libs alias non _internal to _internal. These method definitions are
// only used within the crtdll to provide targets for aliasobj in the crt import lib.
_CRTIMP_PURE void __CLR_OR_THIS_CALL _type_info_dtor_internal_method(void);
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL _name_internal_method(__type_info_node* __ptype_info_node) const;
#endif
#endif
}; #if _HAS_EXCEPTIONS _STD_BEGIN using ::type_info; _STD_END #if !defined(_CRTBLD) || !defined(_TICORE) // This include must occur below the definition of class type_info
#include <exception> _STD_BEGIN class _CRTIMP_PURE bad_cast : public exception {
public:
#ifdef _M_CEE_PURE
__CLR_OR_THIS_CALL bad_cast(const char * _Message = "bad cast")
: exception(_Message)
{}
__CLR_OR_THIS_CALL bad_cast(const bad_cast &_That)
: exception(_That)
{}
virtual __CLR_OR_THIS_CALL ~bad_cast() _NOEXCEPT
{}
#if defined(_CRTBLD) && defined(CRTDLL)
private:
// This is aliased to public:bad_cast(const char * const &) to provide
// the old, non-conformant constructor.
__CLR_OR_THIS_CALL bad_cast(const char * const * _Message)
: exception((const char *)_Message)
{ }
#endif /* _CRTBLD && CRTDLL */
#else /* _M_CEE_PURE */
__CLR_OR_THIS_CALL bad_cast(const char * _Message = "bad cast");
__CLR_OR_THIS_CALL bad_cast(const bad_cast &);
virtual __CLR_OR_THIS_CALL ~bad_cast() _NOEXCEPT;
#if defined(_CRTBLD) && defined(CRTDLL)
private:
// This is aliased to public:bad_cast(const char * const &) to provide
// the old, non-conformant constructor.
__CLR_OR_THIS_CALL bad_cast(const char * const * _Message);
#endif /* _CRTBLD && CRTDLL */
#endif /* _M_CEE_PURE */
}; class _CRTIMP_PURE bad_typeid : public exception {
public:
#ifdef _M_CEE_PURE
__CLR_OR_THIS_CALL bad_typeid(const char * _Message = "bad typeid")
: exception(_Message)
{}
__CLR_OR_THIS_CALL bad_typeid(const bad_typeid &_That)
: exception(_That)
{}
virtual __CLR_OR_THIS_CALL ~bad_typeid() _NOEXCEPT
{}
#else /* _M_CEE_PURE */
__CLR_OR_THIS_CALL bad_typeid(const char * _Message = "bad typeid");
__CLR_OR_THIS_CALL bad_typeid(const bad_typeid &);
virtual __CLR_OR_THIS_CALL ~bad_typeid() _NOEXCEPT;
#endif /* _M_CEE_PURE */ }; class _CRTIMP_PURE __non_rtti_object : public bad_typeid {
public:
#ifdef _M_CEE_PURE
__CLR_OR_THIS_CALL __non_rtti_object(const char * _Message)
: bad_typeid(_Message)
{}
__CLR_OR_THIS_CALL __non_rtti_object(const __non_rtti_object &_That)
: bad_typeid(_That)
{}
virtual __CLR_OR_THIS_CALL ~__non_rtti_object() _NOEXCEPT
{}
#else /* _M_CEE_PURE */
__CLR_OR_THIS_CALL __non_rtti_object(const char * _Message);
__CLR_OR_THIS_CALL __non_rtti_object(const __non_rtti_object &);
virtual __CLR_OR_THIS_CALL ~__non_rtti_object() _NOEXCEPT;
#endif /* _M_CEE_PURE */
}; _STD_END
#endif // !_CRTBLD || !_TICORE #else _STD_BEGIN // CLASS bad_cast
class _CRTIMP2 bad_cast
: public exception
{ // base of all bad cast exceptions
public:
bad_cast(const char *_Message = "bad cast") _THROW0()
: exception(_Message)
{ // construct from message string
} virtual ~bad_cast() _NOEXCEPT
{ // destroy the object
} protected:
virtual void _Doraise() const
{ // perform class-specific exception handling
_RAISE(*this);
}
}; // CLASS bad_typeid
class _CRTIMP2 bad_typeid
: public exception
{ // base of all bad typeid exceptions
public:
bad_typeid(const char *_Message = "bad typeid") _THROW0()
: exception(_Message)
{ // construct from message string
} virtual ~bad_typeid() _NOEXCEPT
{ // destroy the object
} protected:
virtual void _Doraise() const
{ // perform class-specific exception handling
_RAISE(*this);
}
}; class _CRTIMP2 __non_rtti_object
: public bad_typeid
{ // report a non RTTI object
public:
__non_rtti_object(const char *_Message)
: bad_typeid(_Message)
{ // construct from message string
}
};
_STD_END
#endif /* _HAS_EXCEPTIONS */ #endif /* RC_INVOKED */ #pragma pop_macro("new")
#pragma pack(pop)
#pragma warning(pop) #endif // _TYPEINFO_ /*
* Copyright (c) Microsoft Corporation. ALL RIGHTS RESERVED.
* Modified January 1996 by P.J. Plauger
* Modified November 1998 by P.J. Plauger
* Consult your license regarding permissions and restrictions.
V6.00:0009 */

对于源码可以简单解释为:

class type_info {
public:
//析构函数
_CRTIMP virtual ~type_info();
//重载的==操作符
_CRTIMP int operator==(const type_info& rhs) const;
//重载的!=操作符
_CRTIMP int operator!=(const type_info& rhs) const;
_CRTIMP int before(const type_info& rhs) const;//用于type_info对象之间的排序算法
//返回类的名字
_CRTIMP const char* name() const;
_CRTIMP const char* raw_name() const;//返回类名称的编码字符串
private:
//各种存储数据成员
void *_m_data;
char _m_d_name[];
//将拷贝构造函数与赋值构造函数设为了私有
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};

因为type_info类的复制构造函数和赋值运算符都是私有的,所以不允许用户自已创建type_info的类。唯一要使用type_info类的方法就是使用typeid函数。

typeid函数

typeid函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typeid(a).name()就能知道变量a是什么类型的。typeid()函数的返回类型为typeinfo类型的引用。

typeid函数是type_info类的一个引用对象,可以访问type_info类的成员。但因为不能创建type_info类的对象,而typeid又必须反回一个类型为type_info类型的对象的引用,所以怎样在typeid函数中创建一个type_info类的对象以便让函数反回type_info类对象的引用就成了问题。这可能是把typid函数声明为了type_info类的友元函数来实现的,默认构造函数并不能阻止该类的友元函数创建该类的对象。所以typeid函数如果是友元的话就可以访问type_info类的私有成员,从而可以创建type_info类的对象,从而可以创建返回类型为type_info类的引用。

例如:

class A{
private:
A(){}
A(const A&){}
A& operator = (const A&){}
friend A& f();
};

函数f()是类A的友元,所以在函数f()中可以创建类A的对象。同时为了实现函数f()反回的对象类型是A的引用,就必须在函数f中创建一个类A的对象以作为函数f的反回值,比如函数f可以这样定义:

A &f()
{
A m_a;
return m_a;
}

因为typeid函数是type_info类的对象,也就是说可以用该函数访问type_info类的成员,即type_info类中重载的==和!=运算符,name()和before()成员函数,比如typid(a).name()和typid(a)==typid(b)等等。

class A{
private:
A(){ b = ; cout << "A\n"; }
public:
void name()
{
cout << "Class Name is A\n";
}
friend A &f();
private:
int b;
};
A &f()
{
A friend_A;
cout << "The function of Class A\n";
return friend_A;
}
int main()
{
f().name();
return ;
}

运行截图:

C++中的RTTI机制解析

函数f()是类A的友元,且返回一个类A的对象,因为f()函数是类A的友元,所以在函数f中可以用默认构造函数创建类A的对象,这时函数f()同时是一个函数,也是类A的对象,因此也可以访问类A中的成员。

typeid函数的使用示例:

class A{
private:
int a;
}; class B :public A{
public:
virtual void f(){ cout << "HelloWorld\n"; }
private:
int b;
}; class C :public B{
public:
virtual void f(){ cout << "HelloWorld++\n"; }
private:
int c;
}; class D :public A{
public:
virtual void f(){ cout << "HelloWorld--\n"; }
private:
int d;
};
int main()
{
int a = ;
cout << typeid(a).name() << endl;
A objA;
//打印出class A
cout << typeid(objA).name() << endl;
B objB;
//打印出class B
cout << typeid(objB).name() << endl;
C objC;
//打印出class C
cout << typeid(objC).name() << endl; //以下是多态在VC 6.0编译器不支持,但是在GCC以及微软更高版本的编译器却都是
//支持的,且是在运行时候来确定类型的,而不是在编译器,会打印出class c
B *ptrB=new C();
cout<<typeid(*ptrB).name()<<endl; A *ptrA = new D();
//打印出class A而不是class D
cout << typeid(*ptrA).name() << endl;
return ;
}

运行截图:
C++中的RTTI机制解析

dynamic_cast强制转换运算符

该转换符用于将一个指向派生类的基类指针或引用转换为派生类的指针或引用,注意dynamic_cast转换符只能用于含有虚函数的类,其表达式为dynamic_cast<类型>(表达式),其中的类型是指把表达式要转换成的目标类型,比如含有虚函数的基类B和从基类B派生出的派生类D,则B *pb; D *pd, md; pb=&md; pd=dynamic<D*>(pb); 最后一条语句表示把指向派生类D的基类指针pb转换为派生类D的指针,然后将这个指针赋给派生类D的指针pd,有人可能会觉得这样做没有意义,既然指针pd要指向派生类为什么不pd=&md;这样做更直接呢?

因为有些时候我们需要强制转换,比如如果指向派生类的基类指针B想访问派生类D中的除虚函数之外的成员时就需要把该指针转换为指向派生类D的指针,以达到访问派生类D中特有的成员的目的,比如派生类D中含有特有的成员函数g(),这时可以这样来访问该成员dynamic_cast<D*>(pb)->g();因为dynamic_cast转换后的结果是一个指向派生类的指针,所以可以这样访问派生类中特有的成员。但是该语句不影响原来的指针的类型,即基类指针pb仍然是指向基类B的。

dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换,如果能成功转换则转换之,如果转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常,所以在使用dynamic_cast转换之间应使用if语句对其转换成功与否进行测试,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者这样测试if(dynamic_cast<D*>(pb)){…}else{…}。

对于其它的强制转换运算符:static_cast,reinterpret_cast,const_cast,请阅读C++中“强制转换”的四大天王

C++中的RTTI机制解析的更多相关文章

  1. Dubbo中SPI扩展机制解析

    dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name, ...

  2. C&plus;&plus; 中的RTTI机制详解

    前言 RTTI是”Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法.RTTI并不是什么新的东西,很早就有了这个技术,但是,在实际应用中使 ...

  3. &lbrack;置顶&rsqb; C&plus;&plus;中RTTI机制剖析

    C++中要想在运行时获取类型信息,可没有Java中那么方便,Java中任何一个类都可以通过反射机制来获取类的基本信息(接口.父类.方法.属性.Annotation等),而且Java中还提供了一个关键字 ...

  4. 深度解析VC中的消息传递机制

    摘要:Windows编程和Dos编程,一个很大的区别就是,Windows编程是事件驱动,消息传递的.所以,要学好Windows编程,必须 对消息机制有一个清楚的认识,本文希望能够对消息的传递做一个全面 ...

  5. Java RTTI机制与反射机制

    1.1 什么是RTTI? *的定义:In computer programming, RTTI (Run-Time Type Information, or Run-Time Type Iden ...

  6. Delphi 的RTTI机制浅探3(超长,很不错)

    转自:http://blog.sina.com.cn/s/blog_53d1e9210100uke4.html 目录========================================== ...

  7. jQuery中的事件机制深入浅出

    昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...

  8. SQL Server 内存中OLTP内部机制概述(四)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  9. delpi中的RTTI初试

    java中的反射机制使我们能够在运行期间获取运行期类的信息,那么在delphi中有没有这样的功能呢?答案是有,实现这种功能的机制在delphi中叫做RTTI,废话少说,先来一段demo: 1.先定义一 ...

随机推荐

  1. 玩转Windows服务系列——命令行管理Windows服务

    说到Windows服务的管理就不得不说通过命令行的方式管理Windows服务,因为无论是系统管理员,还是通过编程的方式调用cmd命令,命令行都是非常方便以及强大的工具. 接下来就看一下如何通过cmd命 ...

  2. Mac下抓包

    Wireshark针对UNIX Like系统的GUI发行版界面采用的是X Window(1987年更改X版本到X11).Mac OS X在Mountain Lion之后放弃X11,取而代之的是开源的X ...

  3. C&num;反射技术概念作用和要点

    反射(Reflection)是.NET中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类.结构.委托.接口和枚举等)的成员,包括方法.属性.事件,以及构造函数等.还可以获得每个成员的 ...

  4. 【转】 linux下的g&plus;&plus;编译器安装

    再debian下直接apt-get install gcc g++就可以了.按照类似的逻辑,再Fedora下yum install gcc g++ 报告无法找到g++包. 查了一下,原来这个包的名字叫 ...

  5. 【Android工具】DES终结者加密时报——AES加密演算法

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在前面的两篇文章中.我们介绍了DES算法,3DES算法以及他们的Android程序实现,并研究了怎样才干实现 ...

  6. iOS切圆角的几个方法

    这几天在研究到切圆角的方法,也找了下网上的资料 ---------- 切圆角尽量避免离屏渲染. 1.直接用视图中layer中的两个属性来设置圆角,这种方法比较简单,但是及其影响性能不推荐:  @pro ...

  7. 【SDOI2009】学校食堂

    Description 小F的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭.学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴.当然,不同的人口味也不一定相同,但每个人的口味都可以用 ...

  8. Project facet Java version 1&period;8 not supported JDK版本不对无法启动项目解决办法

    https://jingyan.baidu.com/article/6c67b1d69a59a02787bb1e30.html

  9. 洛谷P1073 最优贸易

    题面要求的是一个差值,即走过一条路径能找到的路径上最大值-最小值. 那么相当于跑一遍最长路和一遍最短路,当然不是概念上的最长路最短路,这里把dis[v]的松弛改成用路径上传递来的最大/最小值维护,而不 ...

  10. C语言---指针变量详解3

    指针可以指向一份普通类型的数据,例如 int.double.char 等,也可以指向一份指针类型的数据,例如 int *.double *.char * 等.如果一个指针指向的是另外一个指针,我们就称 ...