自定义Cereal XML输出容器节点

CEREAL_SERIALIZE_INTRUSIVE 在 1.优化Cereal宏 一行声明序列化函数

QString、QVector、QList、QMap序列化在2.在Cereal中支持Qt容器序列化

静态成员函数type_node检测在 3.利用SFINAE检测成员函数

🚀 告别value0:自定义Cereal序列化节点名称的终极方案

—— 让XML输出语义化,告别自动生成的冗余标签


🔧 问题背景:默认序列化的痛点

使用Cereal序列化Qt容器(如QVector<Animal>)时,XML默认生成<value0><value1>等无意义节点:

没改之前,节点是value0…输出为

<?xml version="1.0" encoding="utf-8"?>
<cereal><zoo size="dynamic"><value0><!-- 无意义标签 --><age>1</age><type>1</type><name>a1</name><master>m1</master></value0><value1><age>2</age><type>2</type><name>b2</name><master>m2</master></value1><value2>.........................................................................</zoo>
</cereal>

这种设计导致:

  1. 可读性差:节点名无法反映数据结构含义
  2. 处理困难:XPath查询需依赖顺序而非语义

改完注册,节点不再是value0…,输出

<?xml version="1.0" encoding="utf-8"?>
<cereal><zoo size="dynamic"><Animal><age>1</age><type>1</type><name>a1</name><master>m1</master></Animal><Animal><age>2</age><type>2</type><name>b2</name><master>m2</master></Animal><Animal>.........................................................................</zoo>
</cereal>

⚙️ 解决方案:动态注册自定义节点名

通过编译期类型检测 + SFINAE模板特化,实现节点名称的语义化替换:

// 1. 定义类型注册宏
#define REGISTER_TYPE_NODE(Type) \static const char* type_node(){ return #Type; };// 2. 检测类型是否注册
template<class T>
struct has_static_member_type_node {template<class TT>static auto test(int) -> decltype(std::decay_t<TT>::type_node(), std::true_type{});template<class> static std::false_type test(...);static constexpr bool value = decltype(test<T>(0))::value;
};// 3. 根据注册状态动态设置节点名
template <class Archive, typename T>
typename std::enable_if<has_static_member_type_node<T>::value, void>::type
customNodeName(Archive& ar, const T& t) {ar.setNextName(std::decay_t<T>::type_node()); // 使用注册名
}

🧠 关键技术解析
技术点作用优势
REGISTER_TYPE_NODE为类型注入type_node()静态函数非侵入式,无需修改原结构定义
SFINAE检测编译期判断类型是否包含type_node()零运行时开销4,6
ar.setNextName()覆盖Cereal默认节点命名逻辑精准控制XML输出结构

💻 完整实现:Qt容器+自定义类型适配

1. 自定义类型注册示例
struct Animal {int age, type;QString name, master;// 序列化成员CEREAL_SERIALIZE_INTRUSIVE(age, type, name, master)// 关键!注册XML节点名REGISTER_TYPE_NODE(Animal) // 输出标签变为<Animal>
};
2. Qt容器序列化改造
namespace cereal {template <class Archive, typename T>typename std::enable_if<!Archive::is_loading::value>::typeserialize(Archive& ar, QVector<T>& vec) {ar(make_size_tag(vec.size()));for (auto&& item : vec) {customNodeName(ar, item); // 动态设置节点名ar(item); // 正常序列化}}
}
3. QString优化(避免二次嵌套)
void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive& ar, QString const& str) {ar.saveValue(str.toStdString()); // 直接输出文本值
}

效果对比

修改前

<value0> <!-- 无意义标签 --><age>1</age><name>a1</name>
</value0>

修改后

<Animal> <!-- 语义化标签 --><age>1</age><name>a1</name>
</Animal>

优化收益

  • 可读性↑:节点名直接反映数据类型
  • 兼容性:完全兼容Cereal反序列化逻辑

🧪 测试验证

// 序列化测试
QList<Animal> animals = {{1,1,"a1","m1"}, {2,2,"b2","m2"}};
cereal::XMLOutputArchive archive(oss);
archive(cereal::make_nvp("zoo", animals));// 输出结果
std::cout << oss.str();

输出验证

<zoo size="dynamic"><Animal> <!-- 自定义节点名 --><age>1</age><type>1</type><name>a1</name></Animal><Animal><age>2</age><type>2</type><name>b2</name></Animal>
</zoo>

💎 总结

通过 REGISTER_TYPE_NODE+customNodeName+SFINAE检测 的三段式设计,实现了:

  1. 零侵入改造:不修改原有类结构
  2. 编译期优化:无运行时性能损失
  3. 格式统一:输出语义化XML,提升数据处理效率

最佳实践:建议结合#define CEREAL_XML_STRING_VALUE "root"使用,实现全链路节点控制


技术改变输出,细节决定体验。让序列化结果不再是一堆冰冷的value0标签,而是充满业务语义的数据蓝图。

核心代码

CEREAL_SERIALIZE_INTRUSIVE 在 1.优化Cereal宏 一行声明序列化函数

QString、QVector、QList、QMap序列化在2.在Cereal中支持Qt容器序列化

静态成员函数type_node检测在 3.利用SFINAE检测成员函数

#pragma region 节点不再是value0...
#define REGISTER_TYPE_NODE(Type) static const char* type_node(){ return #Type;};// 静态成员函数type_node检测
template<class T>
struct has_static_member_type_node {template<class TT>static auto test(int) -> decltype(std::decay_t<TT>::type_node(), std::true_type{});template<class>static std::false_type test(...) {};static constexpr bool value = decltype(test<T>(0))::value;
};template <class Archive, typename T>
typename std::enable_if<has_static_member_type_node<T>::value, void>::type
customNodeName(Archive& ar, const T& t)
{ar.setNextName(std::decay_t<T>::type_node());
}template <class Archive, typename T>
typename std::enable_if<!has_static_member_type_node<T>::value, void>::type
customNodeName(Archive& ar, const T& t)
{// default in xml.hpp std::string getValueName()
}
#pragma endregion namespace cereal
{// 避免XML中生成<value0>节点//! saving string to xmlvoid CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive& ar, QString const& str){ar.saveValue(str.toStdString());}//! loading string from xmlvoid CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive& ar, QString& str){std::string temp;ar.loadValue(temp);str = QString::fromStdString(temp);}// 保存 QVectortemplate <class Archive, typename T>typename std::enable_if<!Archive::is_loading::value, void>::typeserialize(Archive& ar, QVector<T>& vec) {ar(make_size_tag(vec.size())); // number of elementsfor (auto&& item : vec) {
#pragma region 节点不再是value0...customNodeName(ar, item);
#pragma endregion ar(item);}}// 加载 QVectortemplate <class Archive, typename T>typename std::enable_if<Archive::is_loading::value, void>::typeserialize(Archive& ar, QVector<T>& vec) {size_type size;ar(make_size_tag(size));vec.resize(size);for (auto&& item : vec) {ar(item);}}
};// 动物
struct Animal
{int age = 0;int type = 0;QString name;QString master;Animal() = default;Animal(int a,int t,QString n,QString m):age(a), type(t), name(n), master(m){};Animal(const Animal&) = default;Animal(Animal&&) = default;CEREAL_SERIALIZE_INTRUSIVE(age, type, name, master)
#pragma region 节点不再是value0...REGISTER_TYPE_NODE(Animal)
#pragma endregion 
};

测试代码

QList<Animal> list_Animal;
list_Animal.push_back(Animal(1, 1, "a1", "m1"));
list_Animal.push_back(Animal(2, 2, "b2", "m2"));
list_Animal.push_back(Animal(3, 3, "c3", "m3"));
list_Animal.push_back(Animal(4, 4, "d4", "m4"));
list_Animal.push_back(Animal(5, 5, "e5", "m5"));std::ostringstream oss2; // 内存输出流
{cereal::XMLOutputArchive archiveXML(oss2/*, cereal::XMLOutputArchive::Options().indent(true).outputType(false).sizeAttributes(false)*/);archiveXML(::cereal::make_nvp("zoo", list_Animal));
}
std::string xmlStr2 = oss2.str(); // 获取XML字符串
qDebug() << __LINE__ << QString::fromStdString(xmlStr2);// ===== 2. 从字符串反序列化 =====
QList<Animal> list_Animal_in;
std::istringstream iss2(xmlStr2); // 输入流绑定XML字符串
{cereal::XMLInputArchive archiveXMLIn(iss2);archiveXMLIn(cereal::make_nvp("zoo", list_Animal_in));
}
qDebug() << __LINE__ << list_Animal_in.size();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87253.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87253.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87253.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spark 写入hive表解析

FileOutputCommitter中提交mapreduce.fileoutputcommitter.algorithm.version有v1和v2两个版本。 v1版本Spark写入文件的流程&#xff1a; 1.当task完成的时候&#xff0c;会将task的结果文件先写入到临时目录下面。 2.所有的task完成后&#xff0c;将所有的结果文件写入到结…

Linux云计算基础篇(5)

一、sudo是什么&#xff1f; 定义&#xff1a;sudo(SuperUserDO)是一个Linux/Unix系统命令&#xff0c;允许被授权的普通用户以另一个用户&#xff08;通常是超级用户root&#xff09;的身份执行命令。 核心目的&#xff1a; 1.最小权限原则&#xff1a;避免让用户长期拥有ro…

Postgresql通过pgpool进行高可用部署主从,灾备(单机版)

1、bitnami/postgresql-repmgr:15 &#xff08;镜像名&#xff09; Bitnami 的 PostgreSQL-Repmgr 镜像是一个预配置的 Docker 镜像&#xff0c;集成了 PostgreSQL 数据库和 repmgr&#xff08;Replication Manager&#xff09;工具&#xff0c;用于快速搭建高可用&#xff08…

Flink-1.19.0源码详解-番外补充3-StreamGraph图

1.StreamGraph图: StreamGraph是Flink流处理作业的第一个计算调度流图&#xff0c;它是从用户编写的 DataStream API程序转换而来的逻辑图。StreamGraph由StreamNode与StreamEdge组成&#xff0c;StreamNode为记录数据处理的节点&#xff0c;StreamEdge为连接两个StreamNode的边…

linux系统---Nginx反向代理与缓存功能

目录 正向代理和反向代理 正向代理的作用 反向代理可实现的功能 反向代理客户端ip透传 1.初始访问192.168.235.139 结果 2.编辑代理服务器的配置文件 3、重载nginx服务 4、访问代理服务器 实现反向代理负载均衡 1.先启用已用另一台服务端 2.使用192.168.235.140 …

U+平台配置免密登录、安装Hadoop配置集群、Spark配置

文章目录 1、免密登录2、安装hadoop3、Spark配置 具体详细报告见资源部分&#xff0c;全部实验内容已经上传&#xff0c;如有需要请自行下载。 1、免密登录 使用的配置命令&#xff1a; cd ~/.ssh/ssh-keygen -t rsaEnter键回车y回车回车出现如上所示 cat ./id_rsa.pub >…

GitHub vs GitLab 全面对比报告(2025版)

从技术架构到金融估值&#xff0c;深度解析两大代码托管平台的差异化竞争策略 一、技术架构对比 维度GitHub (Microsoft旗下)GitLab (独立上市公司)关键差异核心架构- 分布式Git仓库 Issues/Projects- 全栈DevSecOps平台GitLab集成CI/CD、安全、监控部署模式- SaaS为主 - Git…

Python 数据分析与可视化 Day 14 - 建模复盘 + 多模型评估对比(逻辑回归 vs 决策树)

✅ 今日目标 回顾整个本周数据分析 & 建模流程学会训练第二种模型&#xff1a;决策树&#xff08;Decision Tree&#xff09;掌握多模型对比评估的方法与实践输出综合对比报告&#xff1a;准确率、精确率、召回率、F1 等指标为后续模型调优与扩展打下基础 &#x1fa9c; 一…

本周大模型新动向:KV缓存混合精度量化、个体时空行为生成、个性化问答

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 KVmix: Gradient-Based Layer Importance-Aware Mixed-Precision Quantization for KV Cache 大型语言模型&#xff08;LLMs&#xff09;在推理过程中&#xff0c;键值&#xff08;KV&#xff09;缓存的高内…

在 Spring Boot 中使用 WebMvcConfigurer

WebMvcConfigurer 是 Spring MVC 提供的一个扩展接口&#xff0c;用于配置 Spring MVC 的各种功能。在 Spring Boot 应用中&#xff0c;通过实现 WebMvcConfigurer 接口&#xff0c;可以定制和扩展默认的 Spring MVC 配置。以下是对 WebMvcConfigurer 的详细解析及其常见用法。…

w-笔记:uni-app的H5平台和非H5平台的拍照识别功能:

uni-app的H5平台和非H5平台的拍照识别功能&#xff1a; <template><view class"humanVehicleBinding"><view v-if"warn" class"shadow"></view><view class"header"><uni-nav-bar left-icon"l…

TCP 半连接队列和全连接队列(结合 Linux 2.6.32 内核源码分析)

文章目录 一、什么是 TCP 半连接队列和全连接队列二、TCP 全连接队列1、如何查看进程的 TCP 全连接队列大小&#xff1f;注意 2、TCP 全连接队列溢出问题注意 3、TCP 全连接队列最大长度 三、TCP 半连接队列1、TCP 半连接队列溢出问题2、TCP 半连接队列最大长度3、引申问题 一、…

linux下fabric环境搭建

参考教程&#xff1a; https://devpress.csdn.net/cloudnative/66d58e702045de334a569db3.html?dp_tokeneyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjA2MzY4NywiZXhwIjoxNzQwMzY4MDc0LCJpYXQiOjE3Mzk3NjMyNzQsInVzZXJuYW1lIjoiaHVhbmd0dXBpIn0.oh8e4F6Sw_A4SV2ODQ5W0pYK0…

Redis Pipeline介绍:提高操作Redis数据库的执行效率

Redis Pipeline是一种用于提高Redis执行效率的技术&#xff0c;通过减少客户端与服务器之间的通信开销&#xff0c;显著提升批量操作的性能。本文将详细介绍Redis Pipeline的概念、使用场景、实现方式及其优势。 一、Redis Pipeline的概念 Redis Pipeline是一种批处理机制&am…

linux长时间锁屏无法唤醒

是的&#xff0c;您这么理解很直接&#xff0c;抓住了要点。 简单来说&#xff0c;就是这样&#xff1a; 电脑睡觉有两种方式&#xff1a; 打个盹&#xff08;挂起/Suspend&#xff09;&#xff1a; 把工作状态保存在内存里。这个一般和 Swap 分区没关系。睡死过去&#xff…

STM32F103_Bootloader程序开发11 - 实现 App 安全跳转至 Bootloader

导言 想象一下&#xff0c;我们的单片机 App 正在稳定地运行着&#xff0c;突然我们想给它升级一下&#xff0c;添加个新功能。我们该如何安全地通知它&#xff1a;“嘿&#xff0c;准备好接收新固件了” ? 这就需要 App 和 Bootloader 之间建立一个可靠的"秘密握手"…

Explain解释

参考官方文档&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/explain-output.html explain关键字可以分析你的查询语句的结构和性能。 explain select查询&#xff0c; 执行会返回执行计划的信息。 注意&#xff1a;如果from中有子查询&#xff0c;仍然会执行该子查询…

选择 PDF 转 HTML 转换器的 5 个关键特性

市面上有很多 PDF 转 HTML 的转换器&#xff0c;每一款产品都有不同的功能组合。要理清并理解每个功能可能会让人感到困惑。那么&#xff0c;真正重要的是什么呢&#xff1f; 这篇文章将介绍我们认为在选择最佳 PDF 转 HTML 转换器时最重要的 5 个关键特性&#xff1a; 1. 转换…

使用堡塔在服务器上部署宝塔面板-linux版

使用堡塔在服务器上部署宝塔面板-linux版 使用堡塔多机管理登录服务器 进入宝塔官网&#xff0c;获取安装脚本 wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh && sudo bash install_panel.sh ed8484bec3. 在堡塔多机管理中&#xff0c;…

【Unity高级】Unity多界面游戏场景管理方案详解

引言&#xff1a;游戏界面管理的挑战 在Unity游戏开发中&#xff0c;尤其是包含多个功能界面&#xff08;如主菜单、关卡选择、游戏页面、设置和商城&#xff09;的游戏&#xff0c;如何高效管理场景与界面是架构设计的核心挑战。本文将深入探讨三种主流实现方案&#xff1a;单…