Domanda

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.

Edit:

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;
}
È stato utile?

Soluzione

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.

Altri suggerimenti

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.

Hiding members is good. But when you let the user edit/see them, you should impose some constraints: in each setter there should be a check before assigning the element a new value (which might even make your application crash). Otherwise, there is little difference if the data was public.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top