如何在编译时生成密集的唯一类型ID? [英] How can I generate dense unique type IDs at compile time?

查看:136
本文介绍了如何在编译时生成密集的唯一类型ID?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



<$ p

我尝试创建一个小对象的类系统,基类有一个唯一的标识符标识该类的成员: $ p> class Shape
{
public:
unsigned char id;
};

template< typename T>
类三角形: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 *向量,并打开基类,然后为不同的行为(对于三角形,正方形或ShapeBox和子类形状分别保存在示例类层次结构中)的静态强制转换



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



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

  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 ++有编译时间反射?

解决方案


类型编译器可见的ID
在编译时,密集并自动分配,可能使用
模板元编程计数器


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



核心 - type_id_map 头文件:

  #ifndef __TYPE_ID_MAP__ 
#define __TYPE_ID_MAP__

命名空间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> {};

模板<> struct ID_by_T< null_t> ;:ID_T_pair< null_t,-1> {
inline static const _TCHAR * name(){return _T(null_t); }
};
模板<> 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

并使用 type_id_map templated_cls_id.cpp 中的示例:

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

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


#includetype_id_map

//目标类型
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

//现在可以定义目标类别
typedef triangle< int> int_triangle;
typedef triangle< char> char_triangle;

int _tmain(int argc,_TCHAR * argv []){
using namespace std;
使用命名空间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. 类型< => ID 地图是全局的,只在编译时有效。

  2. 类型< =& ID 链接必须使用 DEF_TYPE_ID PREV_TYPE 宏在全局命名空间级别定义。 li>
  3. IDed类型必须在定义之前声明类型< => ID 链接。

  4. 为了在类中使用自身ID,请使用 ID_by_T< self_type> 基类其中self_type是一个自己类型的类。

  5. 为了在模板类中获取自己的ID,请使用 ID_by_T 作为基类其中base_data_type是用于 type< =>的特殊基本类型的模板类。 ID 链接已定义。例如,请参见 int_triangle char_triangle

功能




  • ID是外部的,并且在编译时自动按顺序自动分配,以 ; => ID 链接定义。

  • 对编译器的最低要求:只有 ISO / IEC 14882:2003 SE的标准功能

  • __ COUNTER __ __ LINE __ BOOST_PP_COUNTER 或基于 sizeof 不用于分配ID:没有与它们相关的副作用。

  • type< => id 映射基于编译时已知的外部标识:

    • ID可以分配给每个类型,甚至基本类型。 li>
    • ID_T_pair 模板描述 type< => id 链接。 ID_by_T / T_by_ID 模板是 ID_T_pair 模板的直接后代。

    • 由于 ID_by_T 模板,无需在类型中定义ID(基本类型不可能实现) li>
    • ID类型是通过 ID_by_T< type> :: ID 枚举常量访问的。

    • 通过ID访问 T_by_ID< ID> :: _ Type inner typedef。

    • 可选:类型的名称使用

    • $
    • 未使用
  • >
  • 地图分布式: type< =>

  • 为了在几个TU中访问地图,可以使用特殊的标题。
  • li>
  • 定义组合派生类型不需要地图的任何附加信息。

  • 地图使用特殊的空类型 null_t ID = -1 在没有类型< => ID 链接。


I'm trying to make a system of classes that are small objects, and the base class has a member that is a unique identifier that identifies the class:

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;
};

With the class identifier, I go through a vector of Shape * and switch on the id visible in the base class, then static cast for different behavior (to Triangle, Square, or ShapeBox and child shapes held in it respectively for the example class hierarchy)

I could turn on RTTI, but the space cost seems fairly large, especially when the type information can be implemented as a pointer and the object size might be no bigger than a couple of bytes. There may be millions of small objects, and I really only need static cast anyways.

Currently I can make type identifiers by using statics that are assigned values from a static monotonically incrementing counter:

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;

Ideally I want dense, unique type ID's that are available at compile time, so the compiler can perform well, like converting a switch on the type IDs into a constant time jump table, or at least a binary search tree rather than a linear time if/else chain for what might be a long list of type IDs...

I can use boilerplate at compile time to manually assign every type ID, or I can use object/function pointers from a similar type ID class. Boiler plate is guaranteed to be dense (because we assign it manually) and known at compile time, but it's unmaintainable for template types. Whenever you add a template type to a shape, you have to manually add a new type. The monotonic static counter is maintainable and dense, but unknown at compile time and so compile time optimizations aren't possible, and thread safety may be a concern. The function pointer ID is known at compile time and maintainable, but isn't dense and won't fit into a small id type like a char.

Is there any way to generate type IDs that are visible to the compiler at compile time, dense, and automatically assigned, perhaps using template metaprogramming counter or some preprocessor magic in C++11 or C++14? Or is this not possible until C++ has compile time reflection?

解决方案

Is there any way to generate type IDs that are visible to the compiler at compile time, dense, and automatically assigned, perhaps using template metaprogramming counter

I've developed a code which does this with few restrictions. Two template specializations ID_by_T and T_by_ID define type <=> ID link at compile time. Type's ID is a enum constant. If type <=> ID link is not defined ID_by_T<type>::ID is -1 and T_by_ID<undefinedID>::type is null_t predefined type. DEF_TYPE_ID(type_name) macro generates new ID when defines type <=> ID link. int_triangle and char_triangle show how to get typedef with correct type's ID and inner typedef _MyID_T shows how to get ID of type. The code was compiled with MS VS 2005 C++.

The core -- type_id_map header file:

#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

And the use of type_id_map example in templated_cls_id.cpp:

#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
}

Output:

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>

Requirements and restrictions:

  1. Type <=> ID map is global and works only at compile time.
  2. Type <=> ID link must be defined at global namespace level using DEF_TYPE_ID and PREV_TYPE macro.
  3. "IDed" type must be declared prior to defintion of Type <=> ID link.
  4. In order to get self ID inside a class use ID_by_T<self_type> as a base class where self_type is an own type of class. But why (see below)?
  5. In order to get self ID inside a templated class use ID_by_T<base_data_type> as a base class where base_data_type is a special base type of templated class for wich type <=> ID link is already defined. See int_triangle and char_triangle for example. Also there are other tricks to get defined ID inside a template instance.

Features

  • IDs are external and allocated at compile time automatically sequentially beginning with 0 in the compilation order of type <=> ID link definitions. This is inevitability due to the requirement of the question.
  • Minimal requirements to a compiler: only standard features of ISO/IEC 14882:2003 SE.
  • Macros __COUNTER__, __LINE__, BOOST_PP_COUNTER or based on sizeof are not used to allocate ID: there are no side effects associated with them.
  • The type <=> id map is based on external IDs known at compile time:
    • An ID can be assigned to every type, even to a fundamental type.
    • ID_T_pair template describes type <=> id link. ID_by_T/T_by_ID templates are direct descendants of ID_T_pair template.
    • Due to ID_by_T template it is not necessary to define ID inside of a type (what is impossible for fundamental types).
    • ID by type is accessed with ID_by_T<type>::ID enum constant.
    • Type by ID is accessed with T_by_ID<ID>::_Type inner typedef.
    • Optional: type's name is accessed with name() method of ID_T_pair.
    • The map does not occupy any memory byte if method name() of ID_T_pair is not used.
  • The map is distributed: type <=> id link can be defined in place but at global namespace level.
  • In order to access the map in a few TU a special header may be used.
  • Definition of a composed derived type does not require any additional info for the map.
  • The map uses special null type null_t and ID=-1 returned in absence of type <=> ID link.

这篇关于如何在编译时生成密集的唯一类型ID?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆