很多类库中都提供了对象类型的运行时识别。能够在运行时判断某个对象的类型及父类类型。
简单的类型识别可以用typeid(this).name()获取类型名称,但是无法判断IsKindOf.
参考了一下MFC,其中的CObject及其派生类中都包含了一个CRuntimeClass静态成员。该成员指向父类的静态成员CRuntimeClass。
当判断某个子类对象IsKindOf某个基类对象时,会沿着这个链表向上搜索。
而定义和实现是通过两个宏分别定义实现并将类名赋给相应的CRuntimeClass。
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
最近写一个小东西,实现运行时识别时遇到一个问题,就是一个模板类会作为一个基类,并有几个接口类(会作为接口类对外开放)从该类派生。
这个时候使用宏定义就有问题了,想把模板参数作为类名定义给CRuntimeClass的时候就复杂了(),如果手动去写声明和实现,定义新派生类就太复杂了。不知道有没有人了解这方面的比较好的实现方式?
或者更好的RTTI解决方法(派生关系中能够分辨不同的模板实做类)?

解决方案 »

  1.   

    MFC没有用到typeid,它是完全自己实现的RTTI机制,所有从CObject继承的类都会把类名登记在模块状态表的类名链表中,获取运行时类名时它逐个扫描所有加载模块中的此链表直到找到匹配的类为止。
    从CObject派生的模板类也一样可以支持RTTI,只是不能直接使用 IMPLEMENT_RUNTIMECLASS 宏,因为在宏展开时遇到<>符号会导致编译失败,需要自己直接写代码:
    template<typename T>
    class MyClass : public CObject
    {
      ...
      IMPLEMENT_RUNTIMECLASS_T(MyClass, CObject, T, ...>
    }#define IMPLEMENT_RUNTIMECLASS_T(class_name, base_class_name, tempT, wSchema, pfnNew, class_init) \
        AFX_COMDAT const CRuntimeClass class##class_name##tempT = { \
            #class_name #tempT, sizeof(class class_name<tempT>), wSchema, pfnNew, \
                RUNTIME_CLASS(base_class_name), NULL, class_init }; \
        CRuntimeClass* GetRuntimeClass() const \
            { return RUNTIME_CLASS(class_name<tempT>); }严重注意:由于模板类只能全部实现在头文件中(如果分离到CPP将无法被继承),因此只能在类定义内部直接使用IMPLEMENT_RUNTIMECLASS_T,不可以再用 DECLARE_RUNTIMECLASS
      

  2.   

    to jameshooo:
       我最初也是这么考虑,把MFC宏中runtime的部分分离出来。重新写了宏。把<typename T_Map,typename T_VALUE> 作为一个宏参数 确实遇到了"宏展开时遇到 <>符号会导致编译失败"的问题,以及两个参数中间有逗号的问题。于是也考虑把typename作为参数定义到宏里面。但是因为模板的typename可能有1~2个,还要分别考虑 模板类作为子类 和 模板类作为父类 这两种情况,那么可能就要定义6个类似却不同的宏。感觉不简洁,用起来也有一点不方便。就是不太清楚这么做是不是必须的,想问问有没有简单通用的方法。”由于模板类只能全部实现在头文件中(如果分离到CPP将无法被继承)。“ 模板的实现放在哪儿好的问题也正在困扰着我,多谢提醒~~to cnzdgs:
       使用虚函数获取类名也考虑过,但是CRuntimeClass的静态对象名就不好声明了,原来可以通过##连接类名来声明一个新的静态对象名,
    static CRuntimeClass class##class_name##tempT;
    但是如果使用虚函数作为参数,就连接上函数名称了吧,能连接虚函数返回的字符串作为对象名声明CRuntimeClass对象么?
    还要考虑模板特化的不同实例得有不同的CRuntimeClass对象名称,似乎mfc的那种方式的局限性也只能做到这一步了。
    不知道还有没有其他开源类库实现的 别的rtti的实现方式?
      

  3.   

    还有就是有可能把<typename T_Map,typename T_VALUE>作为一个参数定义在宏里面么?
      

  4.   

    感觉<<深入浅出MFC>>讲得很清楚...
      

  5.   

    to chunyou128 & saylerboxer:
      你们可能没有明白我的意思,我问的不是RTTI,而是当类的派生关系中有模板类的情况下,如何简单实现RTTI.因为模板参数的关系,不可能照搬mfc中那样的实现方式。我又翻了下深入浅出mfc,似乎没有找到这方面的内容。
    举个例子:有个基类:
    CBaseObject
    {
    }
    然后派生一个模板类:
    template <typename T1,typename T2>
    CTemplate:public CBaseObject
    {
    }
    再然后从模板类派生两个接口类(之所以这么做,是因为有多个类<比如这里的CInterface1和CInterface1实现相似>但仅仅是数据类型不同,所以想把操作提取为一个模板类):
    CInterface1:public CTemplate<int,int>
    {
    }
    CInterface2:public CTemplate<string,string>
    {
    }
    在这种情况下如何简单(关键是用起来简单,或者优雅?)实现RTTI.这里虽然CInterface1与CInterface2都派生自CTemplate,但是因为模板参数不同,事实上CTemplate特化为2个不同的类了,所以不能简单使用类名来定义CRuntimeClass 对象了,还需要考虑到模板参数的问题。如果用MFC的实现方式会有几个问题:
    1. jameshooo所说的 “宏展开时遇到 <>符号会导致编译失败”,也就是说不可能把所有模板参数同时作为一个宏参数,所以就必须对不同的模板参数个数分别定义一套实现宏,显得比较麻烦。
    2. CRuntimeClass静态对象名的问题,因为像前面说的那样,需要同时考虑模板参数的问题,就不能简单的使用类名class##class_name来定义,需要再连接模板参数class##class_name##tempT。同样,针对不同个数的模板参数,也需要定义多套不同的宏。所以现在的问题是,实现应该是可以,但是mfc的这种方式实现起来似乎并没有起到简化作用。不知道有没有更优雅简单的方法~~~
      

  6.   

    使用模板实现MFC的RTTI确实会有很多麻烦,即使按照我上面的做了,也难以全部统一,我那种方式只针对一个模板参数,多个模板参数又得重新设计宏。MFC的这些宏定义已经把宏的功能发挥到极致了,但不支持模板类,这也是可以理解的,因为普通类的类名在编译前就能确定,这些宏需要的就是已确定的类名。模板类不同,它的类名只有在实例化(或派生)时才能确定名称,甚至可能永远没有名称(从未被使用),这时候已经晚了,宏在实例化之前就要被展开。其实最好的办法是不要让模板类支持RTTI,而是在模板类的派生类中支持RTTI,在派生类中使用宏时基类名称越过模板类直接使用CObject,这样在运行时类链表中将不存在模板类,而是认为派生类是直接从CObject派生的,就像模板类从未存在过一样。也许不能满足你的需求,但真的很难。
      

  7.   

    对你的具体需求不是很清楚,我的意思是这样:
    #define THIS_CLASS(classname) \
    public: virtual char* TheClassName(){return #classname;}/*
    // 如果需要用静态数据,可以参考这个宏
    #define THIS_CLASS(classname) \
    public: \
    virtual char* TheClassName() \
    { \
    static char name[] = #classname; \
    return name; \
    }
    */class CBaseObject
    {
    THIS_CLASS(CBaseObject)
    };template <typename T1, typename T2>
    class CTemplate : public CBaseObject
    {
    THIS_CLASS(CTemplate)
    };class CInterface1 : public CTemplate <int, int>

    THIS_CLASS(CInterface1)
    };class CInterface2 : public CTemplate <string, string>
    {
    THIS_CLASS(CInterface2)
    };
      

  8.   

    我是没事捣鼓点东西,想做个单独的特殊应用的sdk类库,没有必要拘泥于MFC,这里谈到mfc是因为大家对那个都比较熟(而且能看到那个的实现源码),所以做个参考,以便讨论。to cnzdgs:
       你的意思我明白。但是这样声明CRuntimeClass对像的时候还是无法使用返回的字符串。(比如用宏的时候会声明为 CRuntimeClass classCInterface1;).如果只是返回字符串的话,只是能知道类名,没有办法实现IsKindOf.to jameshooo:
       想了想还是感觉不对,即是使用前面的方法还是有问题。在模板中IMPLEMENT_RUNTIMECLASS的时候还无法确认模板参数,所以也根本无法声明CRuntimeClass对象。所以不管是几个参数,还是无法实现的。
       或者只能规避了,我这里也确实没有必要让模板类支持RTTI,之前可能也是想歪了。它只不过是一个共性操作的抽象集合。使用多继承可能更好一点,也就是压根就不把模板放在派生关系中。
    [code]
    //基类
    CBaseObject 


    //独立的操作集模板
    template <typename T1,typename T2> 
    CTemplate


    //多继承复用模板,但是派生关系还是CBaseObjcet的子类
    CInterface1
    :public CBaseObject,public CTemplate <int,int>


    CInterface2
    :public CBaseObject,public CTemplate <string,string>  
    {
    }
    [/code]
    我的问题基本上算是解决了(结果可能试试才知道),但是还是留下一个疑问,c++中常用的模板类不应该无法实现RTTI,否则使用模板做类库岂不是有很大的局限性?
    再往下纯属讨论~有人有见过其他的RTTI实现方式么?(不拘泥于mfc、windows,C++的就成。)
      

  9.   

    自己仿照MFC实现一个,管理自己的运行时类链表就可以了,最关键是如何构造一个唯一的名称,在MFC中类名本身就是唯一的,在typeinfo中也是用的类型名称,比如char [19]和char [20]就是两种不同的类型。
      

  10.   

    看来你还是没完全明白我的意思,参考下面代码:
    struct MyRuntimeClass
    {
    char* Name;
    MyRuntimeClass* Base;
    };#define THIS_CLASS(classname, baseclassname) \
        public: \
            virtual MyRuntimeClass* GetMyRuntimeClass() \
            { \
                static char name[] = #classname; \
    static MyRuntimeClass mrc = {name, baseclassname::GetMyRuntimeClass()}; \
                return &mrc; \
            } \class CBaseObject
    {
    public:
    virtual MyRuntimeClass* GetMyRuntimeClass()
    {
    static MyRuntimeClass mrc = {"CBaseObject", NULL};
    return &mrc;
    }
    char* GetClassName(){return GetMyRuntimeClass()->Name;}
    MyRuntimeClass* GetBaseClass(){return GetMyRuntimeClass()->Base;}
    };template <typename T1, typename T2>
    class CTemplate : public CBaseObject
    {
    THIS_CLASS(CTemplate, CBaseObject)
    };class CInterface1 : public CTemplate <int, int>

    THIS_CLASS(CInterface1, CTemplate)
    };class CInterface2 : public CTemplate <string, string>
    {
    THIS_CLASS(CInterface2, CTemplate)
    };
      

  11.   

    我觉得《MFC深入浅出》一书讲得很好。
      

  12.   

    先顶一下。。
    LZ,我想接着说一下,能不能把 模板的 <typename T1, typename T2> 中的类型也作为 宏(THIS_CLASS)的参数?
    这样 IsKindOf 的时候,可以把模板的类型也作为一个判定条件呢。。
    由于不太懂模板,说错了还请各位勿怪。。谢谢。
      

  13.   

    哦~明白了。把静态对象放在虚函数中,也是一种实现方式。
    但是没有考虑模板类型参数,跟mfc的实现差不多,能够判断CInterface2 IsKindOf CTemplate,但是不能判断CInterface2 IsKindOf CTemplate<string,string> or IsKindOf CTemplate<int,int>。不过好像是我想歪了,弄复杂了。这种需求可能也不是很必要。貌似也没有见过判断IsKindOf时带模板参数的实现~~~
      

  14.   

    楼上诸位可能误解了楼主的意思,楼主是希望模板类融入MFC的RTTI架构中,这样很多现有的功能都能使用。
      

  15.   

    其实我最初想要的就是 “IsKindOf 的时候,把模板的类型也作为一个判定条件呢。。 ”,就是刚才说的:
    能判断CInterface2 IsKindOf CTemplate <string,string> or IsKindOf CTemplate <int,int>但是一直表达不清楚....
    现在发现似乎是把问题复杂化了。只判断CInterface2 IsKindOf CTemplate可能就够用了。
      

  16.   

    总结一下:
    1.规避的话使用多继承修改原来的设计可以解决了。
    前面贴的代码没显示出来.
    //RTTI实现基类
    class CBaseObject
    {
    };
    //这里不从CBaseObject派生,算是一个单独的类。
    template <typename T1, typename T2>
    class CTemplate 
    {
    };//复用模板实现的子类则从CBaseObject派生实现RTTI,从 CTemplate <int, int>派生复用实现。
    class CInterface1 : public CTemplate<string,string>,public CTemplate <int, int>,

    };这样CTemplate就从派生关系中分离了,也算是问题解决的一个方法。2.就是不考虑模板类型参数,只以模板类的类名做为派生关系判定。
    修改MFC的宏或者cnzdgs提供的方法,都可以解决。3.就是可能没有什么实际意义,纯属吃饱没事的讨论哈~~
    就是能否实现:
    判断CInterface2 IsKindOf CTemplate <string,string> or IsKindOf CTemplate <int,int> 
      

  17.   

    宏是编译时确定,模板是运行时确定的,因此你没办法把模板类的类型参数作为宏的参数,只能是定义一个类从特定类型的模板类继承才能把classname加上类型。
      

  18.   

    是这个不:#pragma once#include <map>
    #include <string>
    using namespace std;
    namespace RuntimeClass_NameSpace
    {
    template<class T>
    class RuntimeClass
    {
    public:
    RuntimeClass(){};
    ~RuntimeClass(){}; protected:
    static map<string,T*(*)()>& s_pfnCreateObject(); //名字和创建派生类函数的地址 public:
    // 注册,注册名,创建该名字类的函数的地址
    static void Register( const string & name, T * (*pfn)() );
    // 由注册名得到BaseObject派生类的对象
    static T * CreateObject( const string & name );
    }; template<class T>
    map<string, T * (*)()>& RuntimeClass<T>::s_pfnCreateObject()
    {
    static map<string, T * (*)()> local_s_pfnCreateObject;
    return local_s_pfnCreateObject;
    } template<class T>
    void RuntimeClass<T>::Register( const string & name, T * (*pfn)() )
    {
    if ( name.empty() || pfn == NULL )
    {
    assert(0);
    return;
    } s_pfnCreateObject().insert( make_pair(name, pfn) );
    } template<class T>
    T * RuntimeClass<T>::CreateObject( const string & name )
    {
    if ( s_pfnCreateObject()[name] == NULL )
    {
    // 未注册的RuntimeClass的类,需要在名为name的class最后加入
    // REGISTER_RUNTIMECLASS(class_name)
    assert(0);
    return NULL;
    } return (T *)(s_pfnCreateObject()[name]());
    }
    }
    //声名
    #define DECLARE_RUNTIMECLASS(base_class, class_name) \
    class GUIRuntimeClass##class_name : public RuntimeClass_NameSpace::RuntimeClass<base_class> \
    { \
    public:\
    GUIRuntimeClass##class_name( const string & name, base_class * (*fun_addr)() ); \
    ~GUIRuntimeClass##class_name(){}; \
    }; #define IMPLEMENT_RUNTIMECLASS(base_class, class_name) \
    base_class * class_name##CreateObject()\
    {\
    return new class_name;\
    }\
    GUIRuntimeClass##class_name::GUIRuntimeClass##class_name( const string & name, base_class * (*fun_addr)() )\
    {\
    RuntimeClass_NameSpace::RuntimeClass<base_class>::Register( name, fun_addr );\
    }\
    GUIRuntimeClass##class_name g_##class_name(#class_name, class_name##CreateObject);