My settings module has some redundant code:
#include <QSettings>
class MySettings
{
public:
// param1
void setParam1(QString param1) { _settings.setValue("param1", param1); }
string param1() { return _settings.value("param1").toString(); }
// param2
void setParam2(int param2) { _settings.setValue("param2", param2); }
int param2() { _settings.value("param2").toInt(); }
// param3
void setParam3(int param3) { _settings.setValue("param3", param3); }
int param3() { _settings.value("param3").toInt(); }
private:
QSettings _settings;
}
I managed to reduce the amount of code to write by using a macro. Here is an example for the QString parameter type:
#define INTSETTING(setter, getter) \
void set##setter(QString getter) { settings.setValue(#getter, getter);} \
QString getter() {return settings.value(#getter).toString();}
Since I'm using C++, I know that macro usage is bad. I'm looking for a cleaner alternative.
I gave a Qt example (QString) but it is a more general question.
The macros make the definition of the above class much simpler:
class MySettings
{
public:
STRINGSETTING(Param1, param1)
INTSETTING(Param2, param2)
INTSETTING(Param3, param3)
STRINGSETTING(DefaultTitle, defaultTitle)
INTSETTING(MaxDocCount, maxDocCount)
private:
QSettings _settings;
}
Macro substitution is a mechanism that provides a string substitution. It can be achieved through "#deifne". It is used to replace the first part with the second part of the macro definition, before the execution of the program. The first object may be a function type or an object.
In C, function-like macros are much similar to a function call. In this type of macro, you can define a function with arguments passed into it. TechVidvan Tutorial: Macros with arguments! In the above example, the compiler finds the name of the macro (AREA(a)) and replaces it with the statement (a*a).
The macro in C language is known as the piece of code which can be replaced by the macro value. The macro is defined with the help of #define preprocessor directive and the macro doesn't end with a semicolon(;). Macro is just a name given to certain values or expressions it doesn't point to any memory location.
You can either answer this in a religious fashion, or you can go back to the old principle: if it makes your code more readable, do it.
There are a lot of people who answer this in a religious way, they just hate the preprocessor and everything that's to do with it, and ban its use from their code.
On the other hand, there are people who routinely define macros to do repetitive task, I have done so on several occasions, most frequently just defining a macro for the use within a single function (used much in the way you can define subfunctions in GNU-C).
I think, the way people think about it is quite similar to the way people think about the goto
statement: Most deamonize its use, others say it has its positive uses and should not be viewed as evil in itself. You need to decide this for yourself.
Here is one way that does not use macros:
class MySettings
{
public:
template <size_t N>
void setParam(QString param) { _settings.setValue(names[N], param); }
template <size_t N, typename T>
T param() { return _settings.value(names[N]).toString(); }
private:
QSettings _settings;
const char* names[3] = { "param1", "param2", "param3" };
}
You change the syntax a little so say e.g. settings.setParam<1>("string")
and settings.param<1, string>()
but in any case names param1, param2
etc were not so informative.
The only inconvenience is that the caller needs to specify the return type of param()
apart from the parameter number. To get rid of this, you can specify all parameter types within MySettings
, like this:
class MySettings
{
using types = std::tuple<string, int, int>;
public:
template<size_t N>
void setParam(QString param) { _settings.setValue(names[N], param); }
template<size_t N>
typename std::tuple_element<N, types>::type
param() { return _settings.value(names[N]).toString(); }
private:
QSettings _settings;
const char* names[3] = { "param1", "param2", "param3" };
}
You could of course further generalize this class to be used as a base for other settings classes. Within the base, the only things that need to be customized are members types
and names
.
However, keep in mind that if parameter names are informative indeed unlike your example, e.g. setTitle
, setColor
etc. then most probably there is no way to avoid macros. In this case, I prefer a macro that generates an entire struct rather than a piece of code within another class, hence probably polluting its scope. So there could be a struct for each individual parameter, generated by a macro given the parameter name. The settings class would then inherit all those individual structs.
EDIT
I "forgot" generalizing toString()
in param()
(thanks @Joker_vD). One way is this:
template<size_t N>
typename std::tuple_element<N, types>::type
param() {
using T = typename std::tuple_element<N, types>::type;
return get_value(type<T>(), _settings.value(names[N]));
}
where get_value<T>()
is a helper function that you need to define and overload for the types supported by QSettings
, calling the appropriate conversion member function for each type, for instance
template<typename V>
string get_value(type<string>, const V& val) { return val.toString(); }
template<typename V>
int get_value(type<int>, const V& val) { return val.toInt(); }
and type
is just a helper struct:
template<typename T>
struct type { };
If QSettings
itself was designed with templates in mind, you wouldn't need this. But you probably wouldn't need a wrapper in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With