C ++代码生成的宏替代方案

时间:2014-02-22 13:23:43

标签: c++ macros coding-style

我的设置模块有一些重新代码:

#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;
}

我设法通过使用宏来减少代码amout。这里是 QString 参数类型的示例:

#define INTSETTING(setter, getter) \
    void set##setter(QString getter) { settings.setValue(#getter, getter);} \
    QString getter() {return settings.value(#getter).toString();}

由于我使用的是C ++,我知道宏用法很糟糕所以我正在寻找更清洁的替代方案。

我给了一个Qt例子(QString),但这是一个更普遍的问题。

编辑:

这使得上述类的定义更加简单:

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

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

private:
    QSettings _settings;
}

3 个答案:

答案 0 :(得分:3)

你可以用宗教的方式回答这个问题,或者你可以回到原来的原则:如果它让你的代码更具可读性,那就去做吧。

有很多人以宗教的方式回答这个问题,他们只是讨厌预处理器以及与之有关的一切,并禁止在代码中使用它。

另一方面,有些人经常定义宏来执行重复性任务,我已经多次这样做了,最常见的是只在一个函数中定义一个宏(在你定义的方式中使用很多) GNU-C中的子功能。

我认为,人们对它的看法与人们对goto陈述的看法非常相似:大多数人对其使用的看法,其他人说它有其积极的用途,不应该被视为邪恶本身。你需要自己决定。

答案 1 :(得分:2)

以下是一种不使用宏的方法:

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" };
}

您稍微更改了语法,例如settings.setParam<1>("string")settings.param<1, string>(),但无论如何,名称param1, param2等都没有提供足够的信息。

唯一的不便是调用者需要指定除参数号之外的param()的返回类型。要摆脱这种情况,您可以在MySettings中指定所有参数类型,如下所示:

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" };
}

您当然可以进一步概括此类以用作其他设置类的基础。在基础内,唯一需要自定义的内容是成员typesnames

但是,请记住,如果参数名称确实与您的示例不同,例如setTitlesetColor等等,那么很可能无法避免使用宏。在这种情况下,我更喜欢一个宏,它生成一个完整的结构而不是另一个类中的一段代码,因此可能会污染其范围。因此,每个参数都可以有一个结构,由给定参数名称的宏生成。然后,settings类将继承所有这些单独的结构。

修改

我“忘记”在toString()中推广param()(感谢@Joker_vD)。一种方法是:

    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]));
    }

其中get_value<T>()是一个辅助函数,您需要为QSettings支持的类型定义和重载,为每种类型调用适当的转换成员函数,例如

 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(); }

type只是一个辅助结构:

 template<typename T>
 struct type { };

如果QSettings本身的设计考虑了模板,那么您就不需要这样了。但是你可能首先不需要包装器。

答案 2 :(得分:1)

隐藏成员很好。但是当你让用户编辑/看到它们时,你应该施加一些约束:在每个setter中,作为检查,然后为元素分配一个新值(甚至可能使你的应用程序崩溃)。 否则,如果数据为public,则几乎没有区别。