I have an inherited code base that I am looking to improve. Below is a "variable wrapper" template class and #defines that i would like to replace with a template only solution, or redesign completely with something else. I have not been able to come up with any solution that would not have to leverage macros or are worse in complexity and clarity than this. I lay it out plainly and hopefully that makes it obvious what this code does.
The code uses hopscotch_map for storage, which is actually irrelevant to the question posed here. It could use std::map
just as well.
The goal would be to either have a complete template solution or find a "better way"(tm) to have this functionality using modern design patterns.
This is the #define/template code in it's complete form:
template< class T >
class CfgBase
{
private:
T m_val;
public:
explicit CfgBase(T t): m_val{t} {}
inline void set(const T t)
{
m_val = t;
}
inline T get() const
{
return m_val;
}
};
void AddConfigData(const string &p, CfgBase< long > *me){
ConfigVals::m_longMap.insert({p, me});
};
void AddConfigData(const string &p, CfgBase< bool > *me); //these have a similar definition as the long above
void AddConfigData(const string &p, CfgBase< double > *me);
void AddConfigData(const string &p, CfgBase< string > *me);
void AddConfigData(const string &p, CfgBase< unsigned long> *me);
#define CFG_DEF_TYPE( _type, _upper, _key, _dflt ) \
class CFG_##_upper : public CfgBase< _type > \
{ public: \
CFG_##_upper(): CfgBase{_dflt} { AddConfigData( _key, this ); } \
}; \
private: \
CFG_##_upper m_##_upper; \
public: \
_type get##_upper() const { return m_##_upper.get(); } \
void set##_upper( const _type v ) { m_##_upper.set( v ); }
#define CFG_DECL_STR( _upper, _key, _dflt ) CFG_DEF_TYPE( string, _upper, _key, _dflt )
#define CFG_DECL_DBL( _upper, _key, _dflt ) CFG_DEF_TYPE( double, _upper, _key, _dflt )
#define CFG_DECL_BOOL( _upper, _key, _dflt ) CFG_DEF_TYPE( bool, _upper, _key, _dflt )
#define CFG_DECL_LONG( _upper, _key, _dflt ) CFG_DEF_TYPE( long, _upper, _key, _dflt )
#define CFG_DECL_ULONG( _upper, _key, _dflt ) CFG_DEF_TYPE( unsigned long, _upper, _key, _dflt )
using CfgLongMap = bhopscotch_pg_map< string, CfgBase< long > * >;
using CfgBoolMap = bhopscotch_pg_map< string, CfgBase< bool > * >;
using CfgDblMap = bhopscotch_pg_map< string, CfgBase< double > * >;
using CfgStrMap = bhopscotch_pg_map< string, CfgBase< string > * >;
using CfgULongMap = bhopscotch_pg_map< string, CfgBase< unsigned long > * >;
You then use it in this fashion (only the native "long" type example follows):
class ConfigVals
{
static CfgLongMap m_longMap;
CFG_DECL_LONG(SomeCustomVariable, "A_CUSTOM_VARIABLE", 12345)
}
For this example, what happens is the CFG_DECL_LONG
macro becomes:
CFG_DEF_TYPE( long, SomeCustomVariable, "A_CUSTOM_VARIABLE", 12345)
which then becomes (remember that macro is contained inside the ConfigVals
class):
class CFG_SomeCustomVariable : public CfgBase< long >
{ public:
CFG_SomeCustomVariable(): CfgBase{12345} { AddConfigData("A_CUSTOM_VARIABLE", this ); }
};
private:
CFG_SomeCustomVariable m_SomeCustomVariable;
public:
long getSomeCustomVariable() const { return m_SomeCustomVariable.get(); }
void setSomeCustomVariable( const long v ) { m_SomeCustomVariable.set( v );}
The end result is a class that looks like this:
class ConfigVals
{
static CfgLongMap m_longMap;
class CFG_SomeCustomVariable : public CfgBase< long >
{ public:
CFG_SomeCustomVariable(): CfgBase{12345} { AddConfigData("A_CUSTOM_VARIABLE", this ); }
};
private:
CFG_SomeCustomVariable m_SomeCustomVariable;
public:
long getSomeCustomVariable() const { return m_SomeCustomVariable.get(); }
void setSomeCustomVariable( const long v ) { m_SomeCustomVariable.set( v );}
}
The result is that ConfigVals
now has a private variable of CfgBase
type that holds the value, that variable is in a lookup map (used elsewhere in the codebase) and ConfigVals
has custom function calls that incorporate the variable name.
If you have another variable of type long, it's as simple as adding another macro call to the ConfigVals
class. If you want another type, you have to make sure you have coverage by manually adding appropriate code in #defines.
Anyone seen anything like this before and is there a better solution?
EDIT: firda wanted usage elaborated. Here it is.
The ConfigVals
class is actually a singleton, it is the centralized source of all configuration variables in a much larger code base:
class ConfigVals{
public:
ConfigVals();
virtual ~ConfigVals();
static ConfigVals& getInstance()
{
static ConfigVals instance_; //lazy init on first access, guaranteed creation
return instance_;
}
// a whole crapload of macros here
// also a "configuration file" loader function
}
usage is pretty simple, whenever we need to use a particular variable:
#define S_CONFIG ConfigVals::getInstance();
S_CONFIG.getSomeCrucialVariableAtThisMoment();
There is a lot of these everywhere too:
if (S_CONFIG.getSomeStupidVar() >= S_CONFIG.getSomeOtherStupidVar()){
...// do some stuff//
}
On instantiation all variables have default values, adjusted by loading a configuration file. The configuration file loader basically does this for every single type (you have to put an additional one of these for every type you want. it sucks, i know):
auto siter = m_strMap.find(key);
if (siter != m_strMap.cend()) {
siter->second->set(f1);
LOG_CONSOLE->debug("CONFIG: {} = {}", key, f1);
return;
}
the configuration file is in this format:
someValue=2234
someOtherValue=astring
nowaThirdValue=3.4
getSomeCustomVariable()
? I mean: wouldn'tCfgBase
be enough (with get and set)? \$\endgroup\$