如何在编译时生成密集的唯一类型ID?

时间:2014-10-26 07:47:11

标签: c++ templates

我试图创建一个小型对象的系统,并且基类的成员是标识该类的唯一标识符:

class Shape
{
public:
    unsigned char id;
};

template <typename T>
class Triangle : public Shape
{
    T triangle_data;
};

template <typename T>
class Square : public Shape
{
    T square_data;
};

template <typename T>
class ShapeBox : public Shape
{
    T shapebox_data;
    Shape * child_shape;
};

使用类标识符,我通过Shape *的向量并切换基类中可见的id,然后针对不同的行为进行静态转换(分别为三角形,正方形或ShapeBox和子形状保存)示例类层次结构)

我可以启用RTTI,但空间成本似乎相当大,特别是当类型信息可以实现为指针并且对象大小可能不大于几个字节时。可能有数百万个小物件,我真的只需要静态铸造。

目前我可以使用从静态单调递增计数器分配值的静态来创建类型标识符:

class TypeID
{
    static size_t counter;

public:
    template<typename T>
    static size_t value()
    {
        static size_t id = counter++;
        return id;
    }
};
size_t TypeID::counter = 1;

理想情况下,我需要在编译时可用的密集,唯一类型ID,因此编译器可以很好地执行,例如将类型ID上的开关转换为常量时间跳转表,或者至少是二进制搜索树而不是线性时间if / else链可能是一长类型ID列表...

我可以在编译时使用样板来手动分配每个类型ID,或者我可以使用来自类似类型ID类的对象/函数指针。锅炉板保证密集(因为我们手动分配)并且在编译时已知,但它对于模板类型是不可维护的。每当向模板添加模板类型时,都必须手动添加新类型。单调静态计数器是可维护和密集的,但在编译时是未知的,因此编译时优化是不可能的,并且线程安全可能是一个问题。函数指针ID在编译时是已知的并且是可维护的,但不是密集的,并且不适合像char这样的小id类型。

有没有办法生成编译时编译器可见,密集和自动分配的类型ID,可能使用模板元编程计数器或C ++ 11或C ++ 14中的一些预处理器魔术?或者这是不可能的,直到C ++有编译时反射?

3 个答案:

答案 0 :(得分:4)

  

有没有办法生成编译器可见的类型ID   在编译时,密集,自动分配,也许使用   模板元编程计数器

我开发了一个代码,这个代码很少有限制。 编译时有两个模板特化ID_by_TT_by_ID定义type <=> ID链接。 Type的ID是枚举常量。如果未定义type <=> ID个链接,则ID_by_T<type>::ID-1T_by_ID<undefinedID>::typenull_t预定义类型。定义DEF_TYPE_ID(type_name)链接时,type <=> ID宏会生成新ID。 int_trianglechar_triangle显示如何获取具有正确类型ID的typedef,内部typedef _MyID_T显示如何获取类型ID。代码是使用MS VS 2005 C ++编译的。

核心 - type_id_map 标头文件:

#ifndef __TYPE_ID_MAP__
#define __TYPE_ID_MAP__

namespace ns_type_ids {
    struct null_t {};
    template<class T, int ID_v>
    struct ID_T_pair {
        enum {ID=ID_v};
        typedef T _Type;
        inline static const _TCHAR * name() { return _T("unknown"); }
    };

    template<class T> struct ID_by_T: ID_T_pair<T, -1> {};
    template<int ID_v> struct T_by_ID: ID_T_pair<null_t, ID_v> {};

    template<> struct ID_by_T<null_t>: ID_T_pair<null_t, -1> {
        inline static const _TCHAR * name() { return _T("null_t"); }
    };
    template<> struct T_by_ID<ID_by_T<null_t>::ID>: ID_by_T<null_t> {};
};

#define PREV_TYPE null_t
#define DEF_TYPE_ID(type_name) \
namespace ns_type_ids { \
    template<> struct ID_by_T<type_name>: ID_T_pair<type_name, ID_by_T<PREV_TYPE>::ID+1> { \
        inline static const _TCHAR * name() { return _T(#type_name); } \
    }; \
    template<> struct T_by_ID<ID_by_T<type_name>::ID>: ID_by_T<type_name> {}; \
};

#endif

templated_cls_id.cpp 中使用 type_id_map 示例:

#include <tchar.h>
#include <iostream>

#ifdef _UNICODE
#define _tcout wcout
#else
#define _tcout cout
#endif


#include "type_id_map"    

//targeted types
struct shape {};

template<class T>
struct data_t: shape {
    T _data;
};

template<class T>
struct triangle: data_t<T>, ns_type_ids::ID_by_T<data_t<T> > {
    typedef data_t<T> _MyID_T;
};

//begin type <=> id map
DEF_TYPE_ID(int)
#undef  PREV_TYPE
#define PREV_TYPE int

DEF_TYPE_ID(double)
#undef  PREV_TYPE
#define PREV_TYPE double

DEF_TYPE_ID(float)
#undef  PREV_TYPE
#define PREV_TYPE float

DEF_TYPE_ID(data_t<int>)
#undef  PREV_TYPE
#define PREV_TYPE data_t<int>

DEF_TYPE_ID(data_t<char>)
#undef  PREV_TYPE
#define PREV_TYPE data_t<char>
//end type <=> id map

//Now targeted classes could be defined
typedef triangle<int> int_triangle;
typedef triangle<char> char_triangle;

int _tmain(int argc, _TCHAR* argv[]) {
    using namespace std;
    using namespace ns_type_ids;
#define out_id(type_name) \
    _T("ID_by_T<") _T(#type_name) _T(">::ID: ") << ID_by_T<type_name>::ID
#define out_name(type_id) \
    _T("T_by_ID<") _T(#type_id) _T(">: ") << T_by_ID<type_id>::name()

    _tcout
        << out_id(null_t) << endl
        << out_id(int) << endl
        << out_id(double) << endl
        << out_id(float) << endl
        << out_id(int_triangle::_MyID_T) << endl
        << out_id(char_triangle::_MyID_T) << endl

        << out_name(-1) << endl
        << out_name(0) << endl
        << out_name(1) << endl
        << out_name(2) << endl
        << out_name(3) << endl
        << out_name(4) << endl
    ;
    return 0;
#undef out_id
#undef out_name
}

输出:

ID_by_T<null_t>::ID: -1
ID_by_T<int>::ID: 0
ID_by_T<double>::ID: 1
ID_by_T<float>::ID: 2
ID_by_T<int_triangle::_MyID_T>::ID: 3
ID_by_T<char_triangle::_MyID_T>::ID: 4
T_by_ID<-1>: null_t
T_by_ID<0>: int
T_by_ID<1>: double
T_by_ID<2>: float
T_by_ID<3>: data_t<int>
T_by_ID<4>: data_t<char>

要求和限制:

  1. Type <=> ID map是全局的,仅在编译时有效。
  2. 必须使用Type <=> IDDEF_TYPE_ID宏在全局命名空间级别定义
  3. PREV_TYPE链接。
  4. “IDed”类型必须在Type <=> ID link。
  5. 定义之前声明
  6. 为了在类中获取自身ID,请使用ID_by_T<self_type>作为基类,其中self_type是自己的类类型。但为什么(见下文)?
  7. 为了在模板化类中获取自身ID,请使用ID_by_T<base_data_type>作为基类,其中base_data_type是已定义的type <=> ID链接的特殊基本类型的模板化类。例如,请参阅int_trianglechar_triangle。还有其他技巧可以在模板实例中获取定义的ID。
  8. 功能

    • ID 外部并在编译时自动顺序分配,以type <=> ID链接定义的编译顺序从0开始。由于问题的要求,这是不可避免的。
    • 对编译器的最低要求:仅ISO/IEC 14882:2003 SE
    • 的标准功能
    • __COUNTER____LINE__BOOST_PP_COUNTER或基于sizeof不会用于分配ID:没有与之相关的副作用。
    • type <=> id地图基于编译时已知的外部ID:
      • 可以为每种类型分配ID,甚至可以分配给基本类型。
      • ID_T_pair模板描述了type <=> id链接。 ID_by_T / T_by_ID模板是ID_T_pair模板的直接后代。
      • 由于ID_by_T模板,没有必要在类型中定义ID(基本类型不可能)。
      • 按类型ID访问ID_by_T<type>::ID枚举常量。
      • 使用T_by_ID<ID>::_Type内部typedef。
      • 访问ID类型
      • 可选:使用name()的{​​{1}}方法访问类型名称。
      • 如果未使用ID_T_pair的方法name(),则地图不会占用任何内存字节。
    • 地图已分发:ID_T_pair链接可以在适当的位置定义,但在全局命名空间级别。
    • 为了在几个TU中访问地图,可以使用特殊的标题。
    • 合成派生类型的定义不需要地图的任何其他信息。
    • 在没有type <=> id链接的情况下,地图使用特殊的空类型null_tID=-1

答案 1 :(得分:3)

今天我开发了另一种解决方案,可以自动为每个模板实例分配ID,而无需为每个“IDed”模板实例定义别名。名为v2的解决方案基于之前称为v1的解决方案。 v1为基本类型分配ID的功能是自动为每个模板实例分配唯一ID。

UPD:关于选择唯一ID分配器的重要说明

这里解决的问题与任务有关,但两个答案都有关。由于要求,该任务的方法意味着唯一的ID分配器:

  

我可以开启RTTI,但空间成本似乎相当大,   特别是当类型信息可以实现为指针时   并且对象大小可能不大于几个字节

  

我想要在编译时可用的密集,唯一类型ID,所以   编译器可以很好地执行,比如转换类型上的开关   将ID转换为常量时间跳转表,或至少是二进制搜索树

如果使用多个ID分配器(在多个库和开发人员的情况下)并且TU必须在IDed类型上接口,那么唯一的方法是使用GUID,其值是稀疏的和非顺序的。但GUID也占用16个字节,需要RTTI和类型反射。否则,尝试构建两个库(绝对具有不同的type <=> id映射)将类型ID连接到一个模块中,或者链接器生成错误(感谢@MooingDuck备注)或者开发人员将干扰他们的不同分配手动获取的类型的ID(使用v2的DEF_TYPE_ID宏)或ID生成器(通过AUTO_TYPE_ID宏调用编译器)。

因此,为了使用类型的int ID,有些情况总是要减少到第一个:

  1. 唯一的ID分配器和所有TU的唯一type <=> ID地图;
  2. TU的界面不依赖于具有type <=> ID链接的“小对象类系统”,因此是每个TU的第一种情况;
  3. 将完成由不同ID分配器生成的type <=> ID地图上的开发人员之间的协商,以获取唯一的type <=> ID地图。
  4. 还有另一种方法,但不符合“生成密集ID”的要求。该方法允许部分地自动生成结构化ID,即诸如enum {FileID, LocalID}; typedef get_id<arg1_t>::res tmpl_arg_1_ID; ...的ID。在这种情况下,必须手动将FileID分配给定义了type <=> ID链接的每个文件。通过使用LocalID宏调用编译器生成__LINE__。模板的LocalID将以下述方式自动分配。使用tmpl_arg_1_ID模板自动获取get_id等模板参数的ID。这种结构化ID的主要优点是它们对于每个库和TU都是静态的和常量的,因为包含文件的内容不变(但版本化成为一个问题)。为了应用这样的结构化ID,可以使用多个嵌套的switch语句,从FileID开始,然后使用LocalID,依此类推。

    v1的功能与差异

    • 每个模板实例自动获取唯一ID,并且不需要任何别名或前向声明,因为ID已分配并定义为内部枚举常量。
    • 模板在模板的特殊“null”实例中使用自定义为枚举常量的唯一ID,例如名为_BaseT的T<null_t, null_t ...>,其中为所有typename参数提供null_t类型。
    • 只有模板insnances的ID被稀疏:它们是根据模板参数的ID计算的散列函数的值和作为_BaseT::ID访问的模板ID。散列函数与MS VS 2005中xhash标头中定义的相同。
    • 现在type <=> id地图在没有ID=0链接的情况下使用了 type <=> ID
    • 默认情况下,模板实例在地图中没有关联的type <=> ID链接。这就是使用get_id模板按类型访问ID的原因。
    • __COUNTER__宏用于减少和简化代码:PREV_TYPE宏不再需要了。
    • 由于前向声明和内部模板实例的ID,因此无需使用v1中的模板data_t
    • 现在使用type <=> id宏定义自动生成ID的AUTO_TYPE_ID(type_name)链接。
    • 也可以使用type <=> id宏来定义DEF_TYPE_ID(type_name, id)链接由另一个分配器(即,手动)分配的ID。但是如果你使用两个宏,碰撞ID分配的解决方案就会成为一个麻烦。

    核心 - type_id_map_t_cnt 标题

    #ifndef __TYPE_ID_MAP_T_CNT__
    #define __TYPE_ID_MAP_T_CNT__
    
    //use it if there is __COUNTER__ macro and rarefied random ID is allowable
    namespace ns_type_ids {
        typedef unsigned int uint;
        typedef unsigned long long ulint;
        typedef unsigned short ushort;
    
        //`type <=> id` link
        struct null_t { enum {ID=__COUNTER__}; };
        template<class T, int ID_v>
        struct ID_T_pair {
            enum {ID=ID_v};
            typedef T _Type;
            inline static const _TCHAR * name() { return _T("unassigned"); }
        };
    
        //accessors for `type <=> id` link
        template<class T> struct ID_by_T: ID_T_pair<T, null_t::ID> {};
        template<int ID_v> struct T_by_ID: ID_T_pair<null_t, ID_v> {};
    
        //predefined `type <=> id` link for null_t and ID=0
        template<> struct ID_by_T<null_t>: ID_T_pair<null_t, null_t::ID> {
            inline static const _TCHAR * name() { return _T("null_t"); }
        };
        template<> struct T_by_ID<ID_by_T<null_t>::ID>: ID_by_T<null_t> {};
    
        //function for generating IDs inside an instance of class template
        //2166136261U and 16777619U constants are from xhash STL implementation
        template<ushort v, uint a=2166136261U>
        struct hash {
            enum : uint {res=(uint)((ulint)16777619U * (ulint)a ^ (ulint)v)};
        };
    
        //ternary operator ?:
        template <bool, class Yes, class No>struct IIF { typedef null_t res; };
        template <class Yes, class No> struct IIF<true, Yes, No> { typedef Yes res; };
        template <class Yes, class No> struct IIF<false, Yes, No> { typedef No res; };
    
        //accessor to ID of type for both `type <=> ID` link and ID of a template instance
        template <class T>
        struct get_id {
            typedef typename IIF<
            //by default there is no `type <=> ID` link for a teamplate instance
            //instead ID is allocated and defined inside.
                ID_by_T<T>::ID == null_t::ID
            ,   T
            ,   ID_by_T<T>
            >::res _IDed;
            // this `::ID` interface coincedences for
            // ID_T_pair, a template instance T and null_t
            enum : uint {res=_IDed::ID};
        };
    };
    
    // DEF_TYPE_ID macro to define `type <=> id` link
    #define DEF_TYPE_ID(type_name, type_id) \
    namespace ns_type_ids { \
        template<> struct ID_by_T<type_name>: ID_T_pair<type_name, type_id> { \
            inline static const _TCHAR * name() { return _T(#type_name); } \
        }; \
        template<> struct T_by_ID<ID_by_T<type_name>::ID>: ID_by_T<type_name> {}; \
    };
    
    // AUTO_TYPE_ID macro to allocate new ID and define `type <=> id` link
    #define AUTO_TYPE_ID(type_name) DEF_TYPE_ID(type_name, __COUNTER__)
    
    #endif /* __TYPE_ID_MAP_T_CNT__ */
    

    在templated_cls_id.cpp中使用type <=> id地图示例

    #include <tchar.h>
    #include <iostream>
    
    #ifdef _UNICODE
    #define _tcout wcout
    #else
    #define _tcout cout
    #endif
    
    #include "type_id_map_t_cnt"
    
    //Now `type <=> id` link definition became very short
    AUTO_TYPE_ID(int)
    AUTO_TYPE_ID(double)
    AUTO_TYPE_ID(float)
    
    //Use forward declaration of a template and a specialization with null_t
    //to define special base type with ID of the template
    template<class T> struct tmpl_id;
    template<> struct tmpl_id<ns_type_ids::null_t>;
    
    //Now "null template" is known for the compiler
    AUTO_TYPE_ID(tmpl_id<ns_type_ids::null_t>)
    
    //The "null template" specialization
    //Realy _BaseT type alias it the "null template" specialization
    template<> struct tmpl_id<ns_type_ids::null_t> {
        //returns the same base ID for every class instance
        typedef tmpl_id<ns_type_ids::null_t> _BaseT;
        //generating ID and defining its container
        typedef ns_type_ids::hash<ns_type_ids::ID_by_T<_BaseT>::ID> _Hash;
        //This is the ID of template tmpl_id
        enum {ID=_Hash::res};
    };
    
    //Now the target template can be defined.
    //tmpl_id<ns_type_ids::null_t> is the base type for all template instances.
    //_BaseT is inherited from the base type.
    template<class T>
    struct tmpl_id: tmpl_id<ns_type_ids::null_t> {
        //unique rarefied calculated ID for every class instance
        typedef ns_type_ids::hash<        
            ns_type_ids::get_id<T>::res
        ,   _BaseT::ID // it is already hash value
                       // and the second calling hash with it is not needed
        > _Hash;
        enum {ID=_Hash::res};
    };
    
    int _tmain(int argc, _TCHAR* argv[]) {
        using namespace std;
        using namespace ns_type_ids;
    
        typedef int int_alias; //for testing behaviour on alias of int
        //Now get_id is used instead of direct access with ID_by_T
    #define out_id(type_name) \
        _T("ID_by_T<") _T(#type_name) _T(">::ID: ") << get_id<type_name>::res
    #define out_name(type_id) \
        _T("T_by_ID<") _T(#type_id) _T(">: ") << T_by_ID<type_id>::name()
    
        _tcout
            << _T("ID_by_T -- getting ID of type") << endl
            << out_id(null_t) << endl
            << out_id(int) << endl
            <<_T("ID_of_T<type_alias> works as expected") << endl
            << out_id(int_alias) << endl
            << out_id(double) << endl
            << out_id(float) << endl
            << out_id(tmpl_id<null_t>) << endl
            << out_id(tmpl_id<int>) << endl
            << out_id(tmpl_id<double>) << endl
            << out_id(tmpl_id<float>) << endl
            /* Next commented line generates an error to indicate
               absence of ID for the char type */
            //<< out_id(tmpl_id<char>) << endl 
            << endl
            << _T("T_by_ID -- getting type or its name by ID") << endl
            << out_name(-1) << endl
            << out_name(0) << endl
            << out_name(1) << endl
            << out_name(2) << endl
            << out_name(3) << endl
            << out_name(4) << endl
            << out_name(5) << endl
        ;
        return 0;
    #undef out_id
    #undef out_name
    }
    

    <强>输出:

    ID_by_T -- getting ID of type
    ID_by_T<null_t>::ID: 0
    ID_by_T<int>::ID: 1
    ID_of_T<type_alias> works as expected
    ID_by_T<int_alias>::ID: 1
    ID_by_T<double>::ID: 2
    ID_by_T<float>::ID: 3
    ID_by_T<tmpl_id<null_t>>::ID: 4
    ID_by_T<tmpl_id<int>>::ID: 225874304
    ID_by_T<tmpl_id<double>>::ID: 225874307
    ID_by_T<tmpl_id<float>>::ID: 225874306
    
    T_by_ID -- getting type or its name by ID
    T_by_ID<-1>: unassigned
    T_by_ID<0>: null_t
    T_by_ID<1>: int
    T_by_ID<2>: double
    T_by_ID<3>: float
    T_by_ID<4>: tmpl_id<ns_type_ids::null_t>
    T_by_ID<5>: unassigned
    

    如果您知道如何计算模板实例的顺序ID,请让我知道重写ns_type_ids::hash: - )

答案 2 :(得分:1)

我认为你要求的东西在C ++中基本上是不可能的。在编译时无法知道计数器,因为个别编译单元彼此不了解,所以你几乎都隐藏在那里。

相反,我正在使用以下方法,它仍然不在“编译时”,但至少在查询类型时不会产生函数调用开销(假设编译器遵循内联),并且是线程安全的。

RuntimeID.h

//-----------------------------------------------
class CNextRuntimeID 
{
protected:
  static long m_NextRuntimeID;
};

//-----------------------------------------------

template<class T>
class CIntegerRuntimeTypeID: public CNextRuntimeID
{
  static const long m_RuntimeID;
public:
  inline static long GetRuntimeID()
  {
    return m_RuntimeID;
  }
};

template<class T> 
const long CIntegerRuntimeTypeID<T>::m_RuntimeID = CNextRuntimeID::m_NextRuntimeID++;

RuntimeID.cpp

long CNextRuntimeID::m_NextRuntimeID = 0;

我对这个实现有很多想法,我相信它是安全的。一个潜在的问题是m_NextRuntimeID理论上可以在其中一个m_RuntimeID之后初始化为零,这显然会导致重复值。但是,至少在VisualStudio下,初始化为零不会生成代码,而基于计数器的初始化则会生成代码。

不幸的是,如果你真的关心代码空间,你可能不喜欢这样一个事实:每个增量都放在一个函数中,而这些函数占用空间。比通常的“静态局部变量”非线程安全方法更少的空间,但仍然是空间。