为属性表创建调试器友好的枚举 [英] Creating debugger-friendly enums for property table

查看:181
本文介绍了为属性表创建调试器友好的枚举的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个情况,我有一个层次结构的类: Widget Doobry 是继承自 Base (在现实中有很多超过2种类型)。对象的每个实例都有一个属性列表。有一些属性是所有对象共有的,以及一些特定于每个项目类型的属性。一个简单的实现可能如下所示:

 枚举PropertyType {
COMMON_SIZE = 0,//第一节:属性common到所有
COMMON_POSITION,
...
WIDGET_PROPERTY_START = 100,//确保有足够的空间进行扩展
WIDGET_DONGLE_SIZE,
WIDGET_TEXT,
...
DOOBRY_PROPERTY_START = 200
DOOBRY_COLOUR
....
};

class Base {
public:
Base();

std :: vector< std :: pair< PropertyType,string>>属性;
};

这实现了一个目标,在调试器中,我可以看到映射到有意义的名称的属性列表。但它有一些缺点:




  • 所有项目的属性已在一个标题中定义(不适合封装)

  • 如果我们为其中一个类添加更多属性,我们必须为每个类的开始位置选择一些任意数字,以便为未来的扩展提供足够的空间。



我的问题是,是否有另一种方法来实现这个目标。一个想法是,我可以使用更大的字符串常量存储和更慢的查找,但有一个优点是更容易使名称是唯一的,每个项目类型可以定义自己的属性。



编辑:
要求属性将被序列化,因此必须随时间稳定(即枚举不会改变)。最多可以有1M个对象,但绝大多数都有空的属性表(因为它们使用默认值)。查找性能比插入更重要,并且执行字符串哈希的性能命中可能是可以忽略的(我们不能测量它是否仍然是因为我们没有写它!)

解决方案

  struct UniqueTag {
friend TagManager;
char const * tag;
UniqueTag(UniqueTag const& other):tag(other.tag){}
UniqueTag():tag(nullptr){}; //能够创建null标签是有用的
bool operator<(UniqueTag const& other)const {return tag< other.tag; }
bool operator ==(UniqueTag const& other)const {return tag == other.tag; }
//做其他运算符
private:
UniqueTag(char const * t):tag(t){}
};

#include< map> //或unordered_map for performance
class TagManager {
std :: map< std :: string,UniqueTag>缓存;
std :: vector< std :: unique_ptr< char []> >数据;
public:
TagManager(){};
UniqueTag GetTag(std :: string s){
auto range = cache.equal_range(s);
if(range.first!= range.second){
return range.first-> second;
}
std :: unique_ptr< char []> str(new char [s.size()+ 1]);
std :: copy(s.begin(),s.end(),&str; [0]);
str [s.length()] ='\0';
UniqueTag retval(str.get());
data.push_back(std :: move(str));
if(s.length()== 0){
retval = UniqueTag(); //空字符串是null标签,所以我们没有两个!
}
cache.insert(range.first,make_pair(std :: move(s),retval));
return retval;
}
};

一个 TagManager 指向字符串的指针。我们可以做快速比较,因为我们比较指针值。将字符串转换为其中一个唯一标记的速度很慢,并且它具有隐藏的单个标记管理器的反模式,但是...



你的 UniqueTag 在自己旁边贴一个哈希,并查看的东西在哈希(用某种断言,没有两个字符串哈希到相同的值在调试 - 生日悖论使得这种情况发生的可能性比人们天真地期望的更大。这会删除单个管理器类(至少在发布中 - 在调试中,您将有一种方法来检测冲突)如果您的散列是确定性的,调试中缺少冲突可能意味着释放中没有冲突) p>

A boost :: variant< enum1,enum2,enum3> 用适当的可视化器和一些操作符重载,独立枚举。或者是 enum es的自制联盟,主要枚举表示它是有效的,顶部它会让你分裂管理所有的地方。在这两种情况下,您导出枚举的类型索引,然后导出枚举值 - 枚举的顺序必须是稳定的,并且每个枚举中的值必须是稳定的,需要整数。为了检查相等性,需要两个整数的链接比较,而不是一个(你可以黑进一个64位的比较,如果这是更快)。


I have a situation where I have a hierarchy of classes: Widget and Doobry are object types that inherit from Base (in reality there's a lot more than 2 types). Each instance of an object has a list of properties. There are some properties that are common to all objects, and some properties that are specific to each item type. A simple implementation might look like this:

enum PropertyType {
  COMMON_SIZE=0,              // first section: properties common to all
  COMMON_POSITION,
  ...
  WIDGET_PROPERTY_START=100,   // ensure enough space for expansion
  WIDGET_DONGLE_SIZE,
  WIDGET_TEXT,
  ...
  DOOBRY_PROPERTY_START=200
  DOOBRY_COLOUR
  ....
};

class Base {
public:
    Base();

    std::vector<std::pair<PropertyType, string>>  properties;
};

This achieves one objective in that in the debugger I can see a list of properties mapped onto meaningful names. However it suffers from some drawbacks:

  • Properties for all items have be defined in one header (not good for encapsulation)
  • We have to pick some arbitrary numbers for each of the class start positions to allow enough space for future expansion if we add more properties to one of the classes.

My question is whether there's another way of achieving this objective. One thought is that I could use string constants which would be larger to store and slower to lookup, but have the advantage that it's easier to make the names unique and each item type can define its own properties.

EDIT: It is required that the properties will be serialised and so must be stable over time (ie the enums don't change). There may be up to 1M objects but the vast majority will have empty property tables (as they use default values). Lookup performance is more important than insertion, and the performance hit of doing a string hashing is probably negligible (we can't measure whether it is yet as we haven't written it!).

解决方案

struct UniqueTag {
  friend TagManager;
  char const* tag;
  UniqueTag( UniqueTag const& other):tag(other.tag) {}
  UniqueTag():tag(nullptr) {}; // being able to create the null tag is useful
  bool operator<( UniqueTag const& other )const{ return tag < other.tag; }
  bool operator==( UniqueTag const& other )const{ return tag == other.tag; }
  // do other operators
private:
  UniqueTag( char const* t ):tag(t) {}
};

#include <map> // or unordered_map for performance
class TagManager {
  std::map<std::string, UniqueTag> cache;
  std::vector< std::unique_ptr< char[] > > data;
public:
  TagManager() {};
  UniqueTag GetTag( std::string s ) {
    auto range = cache.equal_range(s);
    if (range.first != range.second) {
      return range.first->second;
    }
    std::unique_ptr< char[] > str( new char[ s.size()+1 ] );
    std::copy( s.begin(), s.end(), &str[0] );
    str[s.length()] = '\0';
    UniqueTag retval( str.get() );
    data.push_back( std::move(str) );
    if(s.length()==0) {
      retval = UniqueTag(); // empty string is null tag, so we don't have both!
    }
    cache.insert( range.first, make_pair( std::move(s), retval ) );
    return retval;
  }
};

A single TagManager maintains a bunch of unique pointers to strings. We can do fast comparison because we compare on the pointer value. Converting from a string to one of the unique tags is slow, and it has the anti-pattern of a single tag manager implied, but...

Alternative versions include having your UniqueTag stick a hash next to itself, and look things up on the hash (with some kind of assert that no two strings hash to the same value in debug -- birthday paradox makes that happen far more likely than one would naively expect). This gets rid of the single manager class (at least in release -- in debug, you'd have a way to detect collisions. If your hash is deterministic, the lack of collisions in debug could imply no collisions in release).

A boost::variant<enum1, enum2, enum3> with appropriate visualizer and some operator overloads would let you have multiple independent enums. Or a home-brew union over enumes, with a primary enum that says which is valid, with a visualizer on top of it would let you split up management all over the place. In both cases, you export the index of the "type" of enum, then the enum value -- so the order of the enums has to be stable, and the values within each enum have to be stable, but no magic integers are needed. To check for equality, a two-integer chained comparison is needed instead of one (which you could hack into a single 64 bit comparison, if that is faster).

这篇关于为属性表创建调试器友好的枚举的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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