I have a C++ application where main() instantiates an object, MyApp, and then passes the object to a ReadConfig function.
ReadConfig opens a text based file, parses it, and calls the appropriate MyApp methods to configure it.
class MyApp
{
private:
public:
void SetRate(uint16_t);
void EnableLogging(bool);
void SetAddress(uint32_t);
};
I'm trying to make it easy to maintain ReadConfig such that as new public methods are added to MyApp, it can be as simple as updating a table. I have come up with the following solution but I don't like it. It's hard to maintain due to having to make sure I put 0s in the right place.
Below is a example of what I've been able to work out. Any advice to make this better would be appreciated. Note I work in embedded so the C++ compilers we use don't support C++14 & there is no boost. And I would like to avoid using any STL libraries so as to understand the mechanics of doing this myself.
Here is what I have:
enum ARGTYPE {TBOOL, TUINT16, TUINT32};
template<typename TOBJ, typename TARG>
struct TSetting
{
void (TOBJ::*FSet)(TARG);
};
template<typename obj>
struct SETTINGFN
{
const char *setting_name;
ARGTYPE Targ;
TSetting<obj,bool> HBool;
TSetting<obj,uint16_t> HUint16;
TSetting<obj,uint32_t> HUint32;
};
SETTINGFN<MyApp> MyAppSettings[] =
{
"logging" ,TBOOL, &MyApp::EnableLogging, 0,0,0
,"maxrate" ,TUINT16, 0,0,0,&MyApp::SetRate
,"address" ,TUINT32, 0, &MyApp::SetAddress, 0,0
};
unsigned int MyAppSettings_Count = sizeof(MyAppSettings) / sizeof(SETTINGFN<MyApp>);
Then as I read the config file and parse it, I call a function to handle calling the actual MyApp functions through the function pointers. That function is as follows:
bool AppSetting(MyApp &E, TObjnode &node)
{
bool rval = false;
for(unsigned int i=0; i<MyAppSettings_Count && !rval; i++)
{
if(node.GetName() == MyAppSettings[i].setting_name)
{
rval = true;
switch(MyAppSettings[i].Targ)
{
case TBOOL:
(E.*MyAppSettings[i].HBool.FSet)(node.GetValue().AsBool());
break;
case TUINT16:
(E.*MyAppSettings[i].HUint16.FSet)(node.GetValue().Value());
break;
case TUINT32:
(E.*MyAppSettings[i].HUint32.FSet)(node.GetValue().Value());
break;
}
}
}
return(rval);
}
The thorn in your design is the different parameter types.
If the parameters are abstracted into a structure, then the functions can be simplified to one fixed signature.
By using a base structure, the signatures and be more generic:
struct Arguments_Base
{
};
void SetRate(Arguments_Base& ab);
void EnableLogging(Arguments_Base& ab);
void SetAddress(Arguments_Base& ab);
By having uniform signatures, function pointers or function objects can be used in tables or maps, making searching easier. The search engines should be able to be generic and depend on the size of the data so that only the data (e.g. table) needs to be changed, not the search engine.
Each type of set of arguments should be derived from the Arguments_Base
class. The function can then dynamic_cast
the base class reference.
See also: Factory Design Pattern, Visitor Design Pattern, Double Dispatch