代码语言:javascript复制本文通过代码演示SkinConfig对象与QVariantMap怎么方便地转换的思考过程。
struct SkinConfig
{
int width = 0;
int height = 0;
bool visible = false;
float opacity = 1.0;
QColor color = Qt::white;
static SkinConfig fromMap(const QVariantMap &map);
QVariantMap toMap() const;
}
QVariantMap
转SkinConfig
使用的是fromMap
接口,而SkinConfig
转QVariantMap
使用的是toMap
接口。
我们看看他们的实现:
static SkinConfig fromMap(const QVariantMap &map)
{
SkinConfig config;
config.width = map.value("width", 0).toInt();
config.height = map.value("height", 0).toInt();
config.opacity = map.value("opacity", false).toFloat();
config.visible = map.value("visible", false).toBool();
config.color = map.value("color", QColor(Qt::white)).value<QColor>();
return config;
}
QVariantMap toMap() const
{
QVariantMap map;
map["width"] = width;
map["height"] = height;
map["opacity"] = opacity;
map["visible"] = visible;
map["color"] = color;
return map;
}
上面代码,我们会发现,由于使用字符串作为key
值字段,容易写错,且fromMap
和toMap
的字段都需要一致,不然就会转换不完整。还有就是字符串写错了,编译器也不会报错,会增加维护代码的工作量。
既然需要字段一致还要编译器提示,那么我们就试试用宏来实现下吧。大致思路是将width
,height
,opacity
等这些成员变量用#变量名字
转换为字符串。比如:
#define TO_STRING(var) (#var) // 将传递的var替换为字符串"var"
static SkinConfig fromMap(const QVariantMap &map)
{
SkinConfig config;
config.width = map.value(TO_STRING(width), 0).toInt();
config.height = map.value(TO_STRING(height), 0).toInt();
...
return config;
}
QVariantMap toMap() const
{
QVariantMap map;
map[TO_STRING(width)] = width;
map[TO_STRING(height)] = height;
...
return map;
}
上面代码中,我们可以看到使用宏TO_STRING
很方便地将变量转为字符串了,还能受到编译器的语法检查,一举两得啊。
用在实际项目中,用得不太顺手啊,一堆的TO_STRING
字符串,默认值设置和fromMap
的toInt()
,toBool()
转换,这样做太啰嗦了,君君心里想,还是再改改吧。
一步一步来,貌似默认值设置可以从一个空的构造类成员获取,那就写成这样吧。
代码语言:javascript复制static SkinConfig fromMap(const QVariantMap &map)
{
SkinConfig config;
config.width = map.value(TO_STRING(width), SKinConfig().width).toInt();
config.height = map.value(TO_STRING(height), SkinConfig().height).toInt();
...
return config;
}
因为构造的时候已经初始化变量了,所以这就是为什么构造的时候初始化变量的好处了,这里可以让变量的构造初始化和QMap的value接口的默认值传入一致。可是,,,代码更啰嗦了,呜呜,不行,改改改!
想了想,还是用宏替换吧。于是代码就变成这样了:
代码语言:javascript复制#define MAP_TO_OBJECT_ITEM(map, obj, key)
obj.key = map.value(#key, decltype(obj)().key)
#define OBJECT_TO_MAP_ITEM(obj, map, key)
map[#key] = (obj).key
static SkinConfig fromMap(const QVariantMap &map)
{
SkinConfig config;
MAP_TO_OBJECT_ITEM(map, config, width).toInt();
MAP_TO_OBJECT_ITEM(map, config, height).toInt();
...
return config
}
QVariantMap toMap() const
{
QVariantMap map;
OBJECT_TO_MAP_ITEM1(*this, map, width);
OBJECT_TO_MAP_ITEM1(*this, map, height);
...
return map;
}
代码简短了一点点,可是MAP_TO_OBJECT_ITEM
处理后还要自己手动转换数据,这个就很难看了,toMap
的*this
多了个*
传递,也太啰嗦了,改改改。
template <typename T> T &point2Ref(T &t) { return t; }
template <typename T> T &point2Ref(T *t) { assert(t != nullptr); return *t; }
#define MAP_TO_OBJECT_ITEM(map, obj, key)
point2Ref(obj).key =
point2Ref(map).value(#key, std::remove_pointer<decltype(obj)>::type().key)
.value<decltype(point2Ref(obj).key)>();
#define OBJECT_TO_MAP_ITEM(obj, map, key)
point2Ref(map)[#key] = point2Ref(obj).key;
static SkinConfig fromMap(const QVariantMap &map)
{
SkinConfig config;
MAP_TO_OBJECT_ITEM(map, config, width);
MAP_TO_OBJECT_ITEM(map, config, height);
MAP_TO_OBJECT_ITEM(map, config, opacity);
MAP_TO_OBJECT_ITEM(map, config, visible);
MAP_TO_OBJECT_ITEM(map, config, color);
return config;
}
QVariantMap toMap() const
{
QVariantMap map;
OBJECT_TO_MAP_ITEM(this, map, width);
OBJECT_TO_MAP_ITEM(this, map, height);
OBJECT_TO_MAP_ITEM(this, map, opacity);
OBJECT_TO_MAP_ITEM(this, map, visible);
OBJECT_TO_MAP_ITEM(this, map, color);
return map;
}
上面代码中使用到了模板和宏,不得不说,宏和模板太适合实现奇淫技巧的操作了。简单介绍下代码的实现。
- 模板
point2Ref
的作用是将指针转换为引用,其实现是使用模板特化的原理。将指针转为引用,就可以统一使用.
去获取成员变量,而不用区分是指针就用->
,非指针就用.
。 decltype(obj)
获取对象的类型,比如:
int a = 0;
decltype(a) => int
int *b = 0;
decltype(b) => int*
std::remove_pointer
是移除指针的类型,比如:
int *a = 0;
std::remove_pointer(a) => int
std::remove_pointer<decltype(obj)>::type()
就是获取传入对象的默认构造的值对象。比如:
SkinConfig config;
std::remove_pointer<decltype(config)>::type() => SkinConfig()
SkinConfig config = new SkinConfig();
std::remove_pointer<decltype(config)>::type() => SkinConfig()
最后
MAP_TO_OBJECT_ITEM
和OBJECT_TO_MAP_ITEM
传入的map
和obj
无论是指针还是值都能正确识别,大大提升编码效率。当然缺点还是有的,就是字段名字与函数命名绑定了,如果他们不一致就没法用该方法了。
如需保存到配置文件持久化,可以将QVariantMap转为字符串再保存为文件:
代码语言:javascript复制QByteArray data = QJsonDocument::fromVariant(map).toJson();
字符串=>map=>对象:
代码语言:javascript复制QVariantMap map = QJsonDocument::fromJson(data).toVariant().toMap();
skinConfig = SkinConfig::fromMap(map);
大家如果有更好的实现或想法,请留言评论吧。
源码地址:https://github.com/aeagean/QMap2Object