为属性表创建调试友好的枚举

Creating debugger-friendly enums for property table

本文关键字:枚举 调试 属性 创建      更新时间:2023-10-16

我有一个情况,我有一个层次结构的类:WidgetDoobry是继承自Base的对象类型(在现实中有很多超过2种类型)。对象的每个实例都有一个属性列表。有一些属性是所有对象通用的,还有一些属性是特定于每个项目类型的。一个简单的实现可能是这样的:

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

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

  • 所有项目的属性都在一个标头中定义(不利于封装)
  • 我们必须为每个类的起始位置选择一些任意数字,以便在我们向其中一个类添加更多属性时为将来的扩展提供足够的空间。

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

编辑:它要求属性将被序列化,因此必须随着时间的推移保持稳定(即枚举不改变)。最多可能有1M个对象,但绝大多数对象的属性表是空的(因为它们使用默认值)。查找性能比插入更重要,执行字符串散列对性能的影响可能可以忽略不计(我们还没有写它,因此无法衡量它是否可以忽略不计!)。

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()] = '';
    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;
  }
};

单个TagManager维护了一堆指向字符串的唯一指针。我们可以做快速比较,因为我们对指针值进行比较。从字符串转换到唯一标记的速度很慢,而且它隐含了单个标记管理器的反模式,但是…

其他版本包括让UniqueTag在自己旁边粘贴一个哈希值,并在哈希值上查找内容(通过某种断言,在调试中没有两个字符串哈希值相同——生日悖论使得这种情况发生的可能性比人们天真地期望的要大得多)。这样就摆脱了单个管理器类(至少在发布中是这样的)——在调试中,您将有一种检测冲突的方法。如果你的哈希是确定性的,那么在调试中没有冲突就意味着在发布中没有冲突。

一个具有适当的可视化工具和一些操作符重载的boost::variant<enum1, enum2, enum3>可以让你有多个独立的enum。或者在enum上建立一个自制联盟,用一个主enum来说明哪个是有效的,在它上面有一个可视化工具可以让你把管理分散到各个地方。在这两种情况下,您先导出enum"类型"的索引,然后导出enum值——因此enum s的顺序必须是稳定的,并且每个enum中的值必须是稳定的,但不需要神奇的整数。为了检查是否相等,需要一个两个整数的链式比较而不是一个(如果更快的话,您可以将其分解为单个64位比较)。