C++中的RTTI机制解析
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 ;
}
运行截图:
函数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 ;
}
运行截图:
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机制解析的更多相关文章
- Dubbo中SPI扩展机制解析
dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name, ...
- C++ 中的RTTI机制详解
前言 RTTI是”Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法.RTTI并不是什么新的东西,很早就有了这个技术,但是,在实际应用中使 ...
- [置顶] C++中RTTI机制剖析
C++中要想在运行时获取类型信息,可没有Java中那么方便,Java中任何一个类都可以通过反射机制来获取类的基本信息(接口.父类.方法.属性.Annotation等),而且Java中还提供了一个关键字 ...
- 深度解析VC中的消息传递机制
摘要:Windows编程和Dos编程,一个很大的区别就是,Windows编程是事件驱动,消息传递的.所以,要学好Windows编程,必须 对消息机制有一个清楚的认识,本文希望能够对消息的传递做一个全面 ...
- Java RTTI机制与反射机制
1.1 什么是RTTI? 维基百科的定义:In computer programming, RTTI (Run-Time Type Information, or Run-Time Type Iden ...
- Delphi 的RTTI机制浅探3(超长,很不错)
转自:http://blog.sina.com.cn/s/blog_53d1e9210100uke4.html 目录========================================== ...
- jQuery中的事件机制深入浅出
昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...
- SQL Server 内存中OLTP内部机制概述(四)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
- delpi中的RTTI初试
java中的反射机制使我们能够在运行期间获取运行期类的信息,那么在delphi中有没有这样的功能呢?答案是有,实现这种功能的机制在delphi中叫做RTTI,废话少说,先来一段demo: 1.先定义一 ...
随机推荐
- Active Record: 資料庫遷移(Migration) (转)
Active Record: 資料庫遷移(Migration) Programming today is a race between software engineers striving to b ...
- js事件捕获,事件冒泡,事件委托以及DOM事件流
一:DOM事件流: 事件流是从页面接收事件的顺序,DOM2级事件规定事件流包括三个阶段: ①事件捕获阶段:用意在于事件达到目标之前捕获它,在事件捕获阶段事件流模型:document→html→body ...
- 如何获取WIN10 Program Files 文件夹下的文件操作权限
找到指定文件,右键-属性-找到指定用户-授"完全控制权限“--更改文件--恢复默认权限.
- Spring利器之包扫描器
在学习Spring这门技术中为了大大减少applicationContext.xml配置的代码量于是有了包扫描器. 闲话不多说我们马上来实现一下吧 示例架构如下: 第一步我们先来修改我们的配置appl ...
- Linux内核分析——跟踪分析Linux内核的启动过程
万子惠 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 实验部分 menu程序: cd LinuxKernel/ qemu -kernel linux-3.18.6/a ...
- CentOS 7 编译安装 Code::Blocks
CentOS 7 编译安装 Code::Blocks yum install cairo-devel yum install pango-devel yum install atk-devel yum ...
- [JSOI2008][BZOJ1012] 最大数(动态开点线段树)
题目描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值. 限制:L不超过当前数列的长度. 2. 插入操作 ...
- 不同操作系统上屏蔽oracle的操作系统认证方式
windows系统上>如果不想用户通过操作系统验证方式登录,可以修改 sqlnet.ora文件,把 SQLNET.AUTHENTICATION_SERVICES=NTS 前面加#注释掉就可以了. ...
- testNG设置测试的执行顺序
在java类中,设置Test的执行顺序可以使用priority,或者enabled等属性.但是在testng.xml中,需要设置它的 preserve-order="true" 另 ...
- 一加3,CM13蓝牙共享互联网 无效。
一加3准备把4G网络共享给魅族PRO5. 但在一加3的蓝牙设置里怎么勾选都无用. 最后发现在要在PRO5上设置才行. 1.在蓝牙列表中,点击带圈的感叹号. 2.选择“互联网访问”. - --