前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >QMap与对象互转的思考

QMap与对象互转的思考

作者头像
Qt君
发布2023-03-17 15:20:25
9560
发布2023-03-17 15:20:25
举报
文章被收录于专栏:跟Qt君学编程跟Qt君学编程

本文通过代码演示SkinConfig对象与QVariantMap怎么方便地转换的思考过程。

代码语言:javascript
复制
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;
}

QVariantMapSkinConfig使用的是fromMap接口,而SkinConfigQVariantMap使用的是toMap接口。   我们看看他们的实现:

代码语言:javascript
复制
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值字段,容易写错,且fromMaptoMap的字段都需要一致,不然就会转换不完整。还有就是字符串写错了,编译器也不会报错,会增加维护代码的工作量。

  既然需要字段一致还要编译器提示,那么我们就试试用宏来实现下吧。大致思路是将widthheightopacity等这些成员变量用#变量名字转换为字符串。比如:

代码语言:javascript
复制
#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字符串,默认值设置和fromMaptoInt()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多了个*传递,也太啰嗦了,改改改。

代码语言:javascript
复制
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)获取对象的类型,比如:
代码语言:javascript
复制
int a = 0;
decltype(a) => int

int *b = 0;
decltype(b) => int*
  • std::remove_pointer是移除指针的类型,比如:
代码语言:javascript
复制
int *a = 0;
std::remove_pointer(a) => int
  • std::remove_pointer<decltype(obj)>::type()就是获取传入对象的默认构造的值对象。比如:
代码语言:javascript
复制
SkinConfig config;
std::remove_pointer<decltype(config)>::type() => SkinConfig()

SkinConfig config = new SkinConfig();
std::remove_pointer<decltype(config)>::type() => SkinConfig()

最后

MAP_TO_OBJECT_ITEMOBJECT_TO_MAP_ITEM传入的mapobj无论是指针还是值都能正确识别,大大提升编码效率。当然缺点还是有的,就是字段名字与函数命名绑定了,如果他们不一致就没法用该方法了。

  如需保存到配置文件持久化,可以将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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Qt君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档