c++ - 寻找滥用枚举的替代方法

标签 c++ visual-c++ c++11 enums

在我最近一直在帮助的一个项目中,整个代码库依赖于一个巨大的枚举,它被有效地用作美化哈希表的键。现在唯一的问题是它很大,每当枚举发生变化时编译基本上是对已经很大的代码库的重建。这需要永远,我真的很想更换它。

enum Values
{
    Value = 1,
    AnotherValue = 2,
    <Couple Thousand Entries>
    NumValues // Sentinel value for creating arrays of the right size
}

我正在寻找的是替换此枚举的方法,但仍然有一个类型安全的系统(没有未经检查的字符串)并且还与 MSVC2010 兼容(没有 constexpr)。额外的编译开销是可以接受的,因为编译时间可能仍然比重新编译一堆文件更短。

我目前的尝试基本上可以总结为延迟定义值,直到链接时间。

使用示例

GetValueFromDatabase(Value);
AddValueToDatabase(Value, 5);
int TempArray[NumValues];

编辑:编译时和运行时预处理是可以接受的。连同它在运行时基于某种缓存数据结构。

最佳答案

实现此目的的一种方法是使用包装数字 ID 且不能直接实例化的键类,因此强制通过类型安全变量完成引用:

// key.h

namespace keys {

// Identifies a unique key in the database
class Key {
  public:
    // The numeric ID of the key
    virtual size_t id() const = 0;
    // The string name of the key, useful for debugging
    virtual const std::string& name() const = 0;
};

// The total number of registered keys
size_t count();

// Internal helpers. Do not use directly outside this code.
namespace internal {
  // Lazily allocates a new instance of a key or retrieves an existing one.
  const Key& GetOrCreate(const std::string& name, size_t id);
}
}

#define DECLARE_KEY(name) \
   extern const ::keys::Key& name

#define DEFINE_KEY(name, id) \
   const ::keys::Key& name = ::keys::internal::GetOrCreate(STRINGIFY(name), id)

使用上面的代码,键的定义如下所示:

 // some_registration.h
 DECLARE_KEY(Value);
 DECLARE_KEY(AnotherValue);
 // ...

 // some_registration.cpp
 DEFINE_KEY(Value, 1);
 DEFINE_KEY(AnotherValue, 2);
 // ...

重要的是,上面的注册代码现在可以拆分成几个单独的文件,这样你就不需要一次重新编译所有的定义。例如,您可以将注册分解为逻辑分组,如果您添加了一个新条目,则只有一个子集需要重新编译,并且只有实际依赖于相应 *.h 文件的代码才需要重新编译(不再需要更新未引用该特定键值的其他代码)。

用法与之前非常相似:

 GetValueFromDatabase(Value);
 AddValueToDatabase(Value, 5);
 int* temp = new int[keys::count()];

完成此操作的相应 key.cpp 文件如下所示:

namespace keys {
namespace {
class KeyImpl : public Key {
  public:
    KeyImpl(const string& name, size_t id) : id_(id), name_(name) {}
    ~KeyImpl() {}
    virtual size_t id() const { return id_; }
    virtual const std::string& name() const { return name_; }

  private:
    const size_t id_;
    const std::string name_;
};

class KeyList {
  public:
    KeyList() {}
    ~KeyList() {
      // This will happen only on program termination. We intentionally
      // do not clean up "keys_" and just let this data get cleaned up
      // when the entire process memory is deleted so that we do not
      // cause existing references to keys to become dangling.
    }

    const Key& Add(const string& name, size_t id) {
       ScopedLock lock(&mutex_);
       if (id >= keys_.size()) {
         keys_.resize(id + 1);
       }

       const Key* existing = keys_[id]
       if (existing) {
         if (existing->name() != name) {
            // Potentially some sort of error handling
            // or generation here... depending on the
            // desired semantics, for example, below
            // we use the Google Log library to emit
            // a fatal error message and crash the program.
            // This crash is expected to happen at start up.
            LOG(FATAL) 
               << "Duplicate registration of key with ID "
               << id << " seen while registering key named "
               << "\"" << name << "\"; previously registered "
               << "with name \"" << existing->name() << "\".";
         }
         return *existing;
       }

       Key* result = new KeyImpl(name, id);
       keys_[id] = result;
       return *result;
    }

    size_t length() const {
       ScopedLock lock(&mutex_);
       return keys_.size();
    }
  private:
    std::vector<const Key*> keys_;
    mutable Mutex mutex_;
};

static LazyStaticPtr<KeysList> keys_list;
}

size_t count() {
  return keys_list->length();
}

namespace internal {
  const Key& GetOrCreate(const std::string& name, size_t id) {
    return keys_list->Add(name, id);
  }
}
}

正如下面评论中恰当指出的那样,允许分散注册的方法的一个缺点是可能会陷入多次使用相同值的冲突场景(上面的示例代码为此添加了一个错误情况,但这发生在运行时,在编译时出现这样的事情真的很好)。一些缓解这种情况的方法包括运行测试检查此类条件的提交 Hook 或关于如何选择 ID 值的策略以减少重复使用 ID 的可能性,例如指示必须递增和提交的下一个可用 ID 的文件作为分配 ID 的一种方式。或者,假设您被允许重新排列 ID(我在此解决方案中假设您必须保留您已经拥有的当前 ID),您可以更改方法以便从名称自动生成数字 ID(例如,通过采用名称的哈希)并可能使用其他因素(例如 __FILE__)来处理冲突,以便 ID 是唯一的。

关于c++ - 寻找滥用枚举的替代方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27729333/

相关文章:

c++ - sizeof 和按引用传递

c++ -/MP 在 VS2012 中不启用 OpenMP 支持

c++ - 了解 std::array 的 move 语义

c++ - [C++11 : 12. 8/7] 有意义吗?

c++ - CMake 找不到包含文件

c++ - 从偏移位置开始复制内存

c++ - 为什么 std::queue 不支持 clear() 函数?

c++ - 从其他专门函数调用专门函数

c++ - 正确使用 cin 对象?

C++11 Watchdog 类,测试应用程序不想退出