C++中的RTTI机制解析

Posted threenerd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中的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[1];
#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[1];  
    //将拷贝构造函数与赋值构造函数设为了私有  
    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 = 3; 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 0;
}
技术分享

运行截图:

技术分享

函数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 = 2;
    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 0;
}
技术分享

运行截图:
技术分享

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&lt;D*>(pb); if(pd){…}else{…},或者这样测试if(dynamic_cast<D*>(pb)){…}else{…}。

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

写代码是一种艺术,甚于蒙娜丽莎的微笑。
技术分享

以上是关于C++中的RTTI机制解析的主要内容,如果未能解决你的问题,请参考以下文章

C++ 虚函数表解析

Thinking in Java -- 类型信息RTTI

delpi中的RTTI初试

MFC六大核心机制之二:运行时类型识别(RTTI)

Java基础 -- 深入理解Java类型信息(Class对象)与反射机制

C++学习(二六四)RTTI