C++ 寻找滥用枚举的替代方法
在我最近帮助的一个项目中,整个代码库依赖于一个庞大的枚举,该枚举有效地用作美化哈希表的键。现在唯一的问题是它是巨大的,每当枚举发生更改时进行编译基本上是对已经很大的代码库的重建。这需要很长时间,我真的很想更换它C++ 寻找滥用枚举的替代方法,c++,visual-c++,c++11,enums,C++,Visual C++,C++11,Enums,在我最近帮助的一个项目中,整个代码库依赖于一个庞大的枚举,该枚举有效地用作美化哈希表的键。现在唯一的问题是它是巨大的,每当枚举发生更改时进行编译基本上是对已经很大的代码库的重建。这需要很长时间,我真的很想更换它 enum Values { Value = 1, AnotherValue = 2, <Couple Thousand Entries> NumValues // Sentinel value for creating arrays of th
enum Values
{
Value = 1,
AnotherValue = 2,
<Couple Thousand Entries>
NumValues // Sentinel value for creating arrays of the right size
}
编辑:可接受编译时间和运行时预处理。以及在运行时基于某种缓存数据结构 实现这一点的一种方法是使用一个封装数字ID的key类,该类不能直接实例化,因此强制通过类型安全变量进行引用:
// 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);
// ...
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);
}
}
}
重要的是,上面的注册码现在可以分割成几个单独的文件,这样您就不需要一次重新编译所有的定义。例如,您可以将注册拆分为逻辑分组,如果您添加了一个新条目,则只需要重新编译一个子集上的条目,并且只需要重新编译实际依赖于相应的*.h文件的代码(不引用该特定键值的其他代码将不再需要更新)
用法与以前非常相似:
GetValueFromDatabase(Value);
AddValueToDatabase(Value, 5);
int* temp = new int[keys::count()];
完成此操作的相应key.cpp
文件如下所示:
// some_registration.h
DECLARE_KEY(Value);
DECLARE_KEY(AnotherValue);
// ...
// some_registration.cpp
DEFINE_KEY(Value, 1);
DEFINE_KEY(AnotherValue, 2);
// ...
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);
}
}
}
名称空间键{
名称空间{
类KeyImpl:公钥{
公众:
KeyImpl(const string&name,size\t id):id(id),name(name){
~KeyImpl(){}
虚拟大小\u t id()常量{return id\u;}
虚拟常量std::string&name()常量{返回名称}
私人:
const size\u t id\u;
const std::字符串名称;
};
类键列表{
公众:
键列表(){}
~KeyList(){
//这只会在程序终止时发生。我们故意
//不要清理“keys_u3;”,只需清理这些数据即可
//当整个进程内存被删除时,我们不会
//导致对键的现有引用处于悬空状态。
}
常量键和添加(常量字符串和名称、大小\u t id){
ScopedLock锁(互斥锁和互斥锁);
如果(id>=键大小()){
键大小调整(id+1);
}
const Key*existing=keys_u[id]
如果(现有){
如果(现有->名称()!=名称){
//可能是某种错误处理
//或者这里的世代…取决于
//所需的语义,例如,如下所示
//我们使用Google日志库来发出
//出现致命错误消息并使程序崩溃。
//预计这起事故将在启动时发生。
日志(致命)
这种方法有一定的前景。我会有点麻烦,看看是否还有其他方法。如果您想将枚举拆分为多个(独立)中定义的部分头文件,您可能希望确保部件兼容。例如,不同的名称不会使用id两次,反之亦然。当前,您的Add
函数不强制执行此操作。@dyp这就是为什么我正在寻找一种解决方案,该解决方案承诺在仍然能够有中心列表的情况下进行自底向上分组e list可以由静态断言来保证。@dyp,这是一个很好的观点,但通过额外的错误处理很容易实现(尽管错误会在运行时发生)。有关如何分配ID的其他约定/策略也会有所帮助…例如,一个文件指示在代码中注册ID之前必须递增和提交的下一个可用ID。标记为答案,解决方案在很大程度上起到了作用,但我们决定进行更基本的重构。“每当枚举发生更改时进行编译基本上就是对已经很大的代码库进行重建”我想您需要对自动生成过程的支持?例如,如果您使用(C++)标识符来命名这些数字,那么这些标识符需要在使用之前声明。如果这是在一个中心头文件中完成的,并且您需要添加额外的标识符,则构建工具将检测到更改并重新编译包含它的每个源文件。在您的系统中停用此头文件的此类功能是否可行构建过程?