我的设置模块有一些重新代码:
#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;
}
答案 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" };
}
您当然可以进一步概括此类以用作其他设置类的基础。在基础内,唯一需要自定义的内容是成员types
和names
。
但是,请记住,如果参数名称确实与您的示例不同,例如setTitle
,setColor
等等,那么很可能无法避免使用宏。在这种情况下,我更喜欢一个宏,它生成一个完整的结构而不是另一个类中的一段代码,因此可能会污染其范围。因此,每个参数都可以有一个结构,由给定参数名称的宏生成。然后,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
,则几乎没有区别。