Finding alternatives to enumeration abuse

In a project that I recently helped, the entire code base depends on a monstrous enumeration that is effectively used as keys for an illustrious hash table. The only problem now is that it is HUGE, compilation whenever enumeration changes are basically a rebuild for an already large code base. This happens forever, and I really want to LOVE to replace it.

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

What I'm looking for is ways to replace this enum, but still has a system that is typeafe (no unchecked strings) and is also compatible with MSVC2010 (no constexpr). The extra compilation overhead is acceptable, as there may still be less compilation time than recompiling a bunch of files.

My current attempts can basically be summarized as a delay in determining values ​​until link time.

Examples of its use

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

Edit: Compiletime and Runtime preprocessing is acceptable. Along with the fact that it allocates some structure of caching data at runtime.

+6
source share
1 answer

One way to achieve this is with a key class that wraps a numeric identifier and that cannot be directly created, so links are forced to be accessed using a variable of the type:

 // 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) 

Using the code above, the key definition will look like this:

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

It is important to note that the registration code above can now be divided into several separate files, so you do not need to recompile all the definitions at once. For example, you can split the registration into logical groups, and if you add a new record, you will need to recompile only one subset, and only code that really depends on the corresponding * .h file should be recompiled (another code that does not refer to this specific key value, no longer need to be updated).

Usage will be very similar to the previous one:

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

The corresponding key.cpp file for doing this will look like this:

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

As noted in the comments below, one of the drawbacks of the approach that allows decentralization of registration is that it then becomes possible to get into conflict scenarios where the same value is used several times (the above example adds an error for this, but this happens in runtime, when it would be very nice to handle such a thing at compile time). Some ways to mitigate this include commits that run tests that check for such a condition or policy, such as choosing an identifier value that reduces the likelihood of reusing an identifier, such as a file that indicates the next available identifier that needs to be enlarged and sent as a way to extract identifiers . Alternatively, assuming you are allowed to shuffle identifiers (I suggested that in this solution you should keep the current identifiers that you already have), you can change the approach so that the numerical identifier is automatically generated from the name (for example, by accepting hash of the name) and, possibly, use other factors, such as __FILE__, to deal with collisions, so that the identifiers are unique.

+3
source

Source: https://habr.com/ru/post/980395/


All Articles