借助 QT 的反射机制实现数据类的序列化

在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类中有大量数据字段,每次都编写存储或输出数据内容,工作重复量太大。C++ 不支持用户自定义的注解,所以没办法使用类似 java 中类似 Lombok 的插件。但是 QT 的属性系统和 moc 编译系统,为简化数据类的序列化提供了可能性。

设计预期

  1. 保持数据类的简洁,最好通过类似 Lombok 的注解语法,只让声明序列化的数据字段支持序列化;
  2. 自动生成 getter 和 setter 方法;
  3. 支持数据流输入/输出;
  4. 支持 QDebug 直接输出对象内容;
  5. 能自动解包 QPoint、QRect、QVariant 等结构化数据。

设计思路

  1. 使用宏代理注解完成 getter 和 setter 方法的自动生成;
  2. 使用反射系统结合宏定义记录需要序列化的字段信息;
  3. 使用基类完成序列化的模板代码,让数据类继承序列化基类即可完成数据的序列化。

代码实现

以下代码中 Serializable 是数据类的基类,在其 cpp 文件中内置了许多模板代码用于支持处理数据流及 QDebug 操作。宏 SERIALIZE(className) 用于指定需要序列化的类,宏JSONFIELD(field, alias, ...) 用于指定需要序列化的字段。为了保证数据类的轻量化,此处没有使用 QObject 类,而是使用了轻量化的 Q_GADGET,这样在预编译时不会产生太多的代码。

复制代码
// serializable.h
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H

#include "qjsonobject.h"

#include <QObject>
#include <QMetaObject>
#include <QMetaProperty>
#include <QDebug>
#include <QFile>

#include <QtWidgets/QMessageBox>

// SERIALIZE 宏内不能包含 Q_GADGET ,必须在子类中单独使用 Q_GADGET,
// 因为 moc 时不引入其它头文件,此处定义Q_GADGET对子类无效,子类不会生成moc文件
//const_cast<className&>(explicit
#define SERIALIZE(className) \
Q_CLASSINFO("base", "Serializable") \
public: \
className(const className &other){ \
    copy(other, *this); \
} \
inline const QMetaObject* getMetaInfo() const override{ \
    return &staticMetaObject; \
}

#ifndef Q_MOC_RUN
// define the tag text as empty, so the compiler doesn't see it
#  define JSON_FLAG
#endif
#define VAR_TYPE(var) decltype(var)

#ifdef CHAIN
#define SETTER(field, alias) \
inline auto set##alias(const __type_##field &value) { \
    field = value; \
return this; \
}
#else
#define SETTER(field, alias) \
inline void set##alias(const __type_##field &value) { \
    field = const_cast<__type_##field>(value); \
}
#endif

#define JSONFIELD(field, alias, ...) \
using __type_##field = decltype(field) ;\
Q_PROPERTY(__type_##field field READ get##alias WRITE set##alias) \
    public: \
    Q_INVOKABLE JSON_FLAG inline QMap<QString, QString> __get##alias##Info__(){ \
        QMap<QString, QString> info; \
        info["name"] = #field; \
        info["alias"] = #alias; \
        info["args"] = QString(#__VA_ARGS__); \
        return info; \
    } \
    inline __type_##field get##alias() const { return field; } \
    inline void set##alias(const __type_##field &value) { \
            field = value; \
    }

class Serializable
{
public:
    Serializable();
    virtual ~Serializable();
    static bool isSubClass(QMetaType type);
    virtual const QMetaObject* getMetaInfo() const = 0;
    void copy(const Serializable &from, Serializable &to);
    void copy(const Serializable &from);
    virtual QString hashCode();
    virtual bool equals(Serializable &obj);
    virtual bool operator==(Serializable &obj);
    virtual QString toString();
    template<typename T>
    T invokeMethod(const QMetaObject *metaInfo, int index);
    virtual QVariant getValue(QString fieldName);
    virtual void setValue(QString fieldName, QVariant value);

    friend QDataStream &operator<<(QDataStream &stream, const Serializable &data);
    friend QDataStream &operator>>(QDataStream &stream, Serializable &data);
    friend QDebug operator<<(QDebug dbg, const Serializable &data);

private:
    QString id;
};

Q_DECLARE_METATYPE(Serializable)

#endif // SERIALIZABLE_H

由于 cpp 文件的代码太多,此处不再贴出来,需要的请到项目 https://github.com/lsyeei/dashboard 的源码目录 /common/ 中查看 serializable.cpp

使用方法

  1. 子类继承 Serializable
  2. 子类中须使用宏 Q_GADGET
  3. 子类中使用宏 SERIALIZE(className) 开启序列化功能
  4. 子类中使用 JSONFIELD(field, alias, ...) 指定哪些字段需要序列化
  5. 使用Q_DECLARE_METATYPE(className)注册元数据类型

说明:

  1. 使用 SERIALIZE 会自动生成拷贝构造函数

  2. 使用 JSONFIELD 指定的字段会自动生成 get和set函数,不必单独声明
    举例:

    class Student : public Serializable
    {
    Q_GADGET
    SERIALIZE(Student)
    public:
    Student();
    ~Student();
    private:
    QString name;
    int age;

    复制代码
      JSONFIELD(name, Name)
      JSONFIELD(age, Age)

    };
    Q_DECLARE_METATYPE(Cunstom)

Student的使用方法:

复制代码
Student student;
student.setName("li");
student.setAge(15);
qDebug() << student;
QByteArray array;
QDataStream stream(&array, QIODeviceBase::WriteOnly);
stream << student;

项目 Compelling Data Designer 用于数据的可视化设计,软件采用可扩展架构,支持扩展图形插件、数据接口。项目仍在开发中,目前已设计完成基本图形、多属性配置、动画等功能。结合 Serializable 类,项目中还提供了 JSON 序列化的实现方式。


相关推荐
又过一个秋16 分钟前
【sylar-webserver】重构日志系统
linux·c++·算法·重构
共享家95271 小时前
C++ vector 核心功能解析与实现
c++
忒可君1 小时前
QT 5.15 程序打包
开发语言·c++·qt
LuckyRich11 小时前
【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现
服务器·c++·http·性能优化
啊阿狸不会拉杆2 小时前
数据结构-数组与广义表
java·c语言·数据结构·c++·python·算法
虾球xz3 小时前
游戏引擎学习第232天
c++·学习·游戏引擎
musk12123 小时前
qt 配置 mysql 驱动问题:Cannot load library qsqlmysql;QMYSQL driver not loaded
qt·mysql
君义_noip3 小时前
信息学奥赛一本通 1504:【例 1】Word Rings | 洛谷 SP2885 WORDRING - Word Rings
c++·算法·图论·信息学奥赛
m0_552200823 小时前
《UE5_C++多人TPS完整教程》学习笔记36 ——《P37 拾取组件(Pickup Widget)》
c++·游戏·ue5