【问题标题】:Macro alternative for C++ code generationC++ 代码生成的宏替代方案
【发布时间】:2014-02-22 13:23:43
【问题描述】:

我的设置模块有一些冗余代码:

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

我设法通过使用宏来减少要编写的代码量。以下是 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;
}

【问题讨论】:

  • 有一个类似的问题:stackoverflow.com/questions/6698073/… 但我认为我的例子是一个更简单的例子。 Moreother 没有接受的答案...
  • 人为什么懒惰?可以节省维护时间吗?一个小脚本将生成代码
  • 我通常让我的 IDE(最好是 Eclipse CDT)做样板的东西,但我不会隐藏或掩盖它。
  • @EdHeal Little 脚本还可以生成编译器现在根据我们的模板定义生成的所有代码,那么为什么要有模板呢?
  • 我认为我目前的宏实现是快速、易读和可维护的。

标签: c++ macros coding-style


【解决方案1】:

您可以以宗教的方式回答这个问题,也可以回到旧的原则:如果它使您的代码更具可读性,那就去做。

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

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

我认为,人们对它的看法与人们对goto 声明的看法非常相似:大多数人将其使用除魔,其他人则说它有其积极的用途,不应被视为本身是邪恶的。您需要自己决定。

【讨论】:

    【解决方案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&lt;1&gt;("string")settings.param&lt;1, string&gt;() 但在任何情况下,名称 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" };
    }
    

    您当然可以进一步概括该类以用作其他设置类的基础。在 base 中,唯一需要定制的是成员 typesnames

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

    编辑

    我“忘记了”在param() 中概括toString()(感谢@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&lt;T&gt;() 是一个辅助函数,您需要为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 本身在设计时就考虑到了模板,那么您就不需要这个了。但你可能一开始就不需要包装器。

    【讨论】:

    • 不应该是typename std::tuple_element&lt;N, types&gt;::type作为返回类型吗?另外,你完全放弃了toInt()
    • @Joker_vD 哎呀对不起,你在这两个方面都是对的。当您不进行测试时,就会发生这种情况。我会编辑。
    • @MartinDelille 好吧,在这种情况下,整个答案都不适用,抱歉。我将尝试详细说明我仅简要描述的宏解决方案,并将其放在另一个答案中。
    • 没问题!无论如何,谢谢你的帮助!我在帖子中放了一个宏解决方案的示例。
    【解决方案3】:

    隐藏成员很好。但是当您让用户编辑/查看它们时,您应该施加一些限制:在每个 setter 中,应该在为元素分配新值之前进行检查(这甚至可能使您的应用程序崩溃) . 否则,如果数据为public,则差别不大。

    【讨论】:

    • 当然,另一种选择是真正使它们成为public,或者继续使用结构。我自己也想过这样说。但是在 OP 的情况下,MySettings 类似乎只是语法糖,以避免直接使用QSettings 的麻烦。这是一个合法的目标和手段,不写访问器是不可能达到同样的效果的。但总的来说,我同意无功能的访问器不是一个好主意。更重要的是,对我来说,它们是一种代码气味,表明数据编程而不是行为。
    猜你喜欢
    • 1970-01-01
    • 2014-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    相关资源
    最近更新 更多