简体   繁体   中英

Macro alternative for C++ code generation

My settings module have some redondant 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 code amout to write by using macro. Here the example for 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 so I'm looking for a cleaner alternative.

I gave a Qt example (QString) but it is a more general question.

Edit:

Which make the definition of the above class much more simpler:

class MySettings
{
public:
    STRINGSETTING(Param1, param1)
    INTSETTING(Param2, param2)
    INTSETTING(Param3, param3)

    STRINGSETTING(DefaultTitle, defaultTitle)
    INTSETTING(MaxDocCount, maxDocCount)

private:
    QSettings _settings;
}

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 eg 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, eg 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM