用最新的C++技术,如何实现一个序列化工具库?

news2024/9/25 14:36:45

在现代C++的发展中,新引入的语言特性为高效且易用的序列化和反序列化库的开发提供了强大的支持。我们今天一起来探索如何在现代C++特性下写出更简洁、更易维护的序列化工具代码。

现有序列化库的挑战

传统的C++序列化库,如Boost.Serialization和Cereal,虽然功能强大,但通常需要额外的设置步骤,如显式注册类和成员,或依赖于预处理器命令。这些需求增加了使用的复杂性,并可能导致代码依赖于特定的编译器特性或外部工具。

截止至C++20,我们实现序列化的可选方案有:

要实现一流的序列化工具,我们要依赖一些非标准的技术或库来在编译期遍历结构体成员。以下是一些方法:

  1. 模板递归与特化: 通过模板递归和特化技术,你可以在编译期迭代结构体的成员,尤其是当你可以定义一些辅助模板结构来存储成员信息时。例如,你可以使用模板结构来存储成员的类型和名称,然后递归地处理这些模板结构。

  2. C++20结构化绑定: 尽管这不是直接遍历所有成员的方法,结构化绑定可以让你更容易地解构结构体,配合模板和constexpr编程,可以部分实现在编译期处理结构体成员的目的。

接下来,我们尝试用这两个技术实现一个系列化库:

模板递归与特化

使用模板递归和特化技术来在编译期迭代结构体的成员涉及到一些高级的C++模板编程技巧。

#include <iostream>
#include <tuple>

// 基本的模板,用于存储成员的信息,即成员的访问器
template<typename T, typename Class, T Class::*Member>
struct MemberInfo {
    using Type = T;
    static constexpr T Class::* pointer = Member;
};

// 结构体,我们想要遍历其成员
struct MyStruct {
    int a;
    double b;
    char c;
};

// 成员信息的定义
using Members = std::tuple<
    MemberInfo<int, MyStruct, &MyStruct::a>,
    MemberInfo<double, MyStruct, &MyStruct::b>,
    MemberInfo<char, MyStruct, &MyStruct::c>
>;

// 递归模板来遍历成员信息
template<std::size_t I, std::size_t N>
struct IterateMembers {
    static void execute(const MyStruct& s) {
        using Member = std::tuple_element_t<I, Members>;
        std::cout << "Member " << I << " value: " << s.*(Member::pointer) << std::endl;
        IterateMembers<I + 1, N>::execute(s);
    }
};

// 特化,停止递归
template<std::size_t N>
struct IterateMembers<N, N> {
    static void execute(const MyStruct&) {}
};

int main() {
    MyStruct s = {10, 3.14, 'z'};
    IterateMembers<0, std::tuple_size<Members>::value>::execute(s);
    return 0;
}

在这个示例中,我们定义了一个名为MemberInfo的模板结构,它可以存储对结构体成员的引用。我们创建了一个名为MyStruct的示例结构体,它包含几个不同类型的成员。为每个成员定义了MemberInfo实例,并将它们存储在std::tuple中。

IterateMembers模板用于递归地访问这个元组中的每个成员。递归在达到元组的末尾时通过模板特化停止。

这个程序将输出MyStruct实例s的每个成员的值。这是一个静态的方式,因为所有的成员必须在编译时被明确指定。这种方法在成员数量和类型在编译时已知的情况下非常有用,但它不具备通用反射机制的灵活性。

这个示例将使用模板特化和递归模式,但这种方法依赖于手动定义一些模板辅助结构来存储关于结构体成员的信息。这不是自动反射,而是一种静态的方式来模拟反射的功能。

C++20结构化绑定

为了使用C++20的结构化绑定特性来在编译期处理结构体成员,我们可以结合使用结构化绑定、模板元编程以及constexpr函数。虽然结构化绑定本身不提供直接遍历所有成员的功能,但我们可以使用它来简化对结构体成员的访问,并结合模板来进行编译期的操作。

下面示例展示如何结合使用C++20的结构化绑定和模板来处理结构体:

#include <iostream>
#include <tuple>
#include <type_traits>

// 定义一个简单的结构体
struct MyStruct {
    int intValue;
    double doubleValue;
    char charValue;
};

// 一个constexpr函数,用于根据类型打印不同的信息
template<typename T>
constexpr void printValue(const T& value) {
    if constexpr (std::is_same_v<T, int>) {
        std::cout << "Int: " << value << std::endl;
    } else if constexpr (std::is_same_v<T, double>) {
        std::cout << "Double: " << value << std::endl;
    } else if constexpr (std::is_same_v<T, char>) {
        std::cout << "Char: " << value << std::endl;
    }
}

// 使用结构化绑定和模板递归处理每个成员
template<typename... Args, std::size_t... Is>
constexpr void processMembers(const std::tuple<Args...>& tpl, std::index_sequence<Is...>) {
    (printValue(std::get<Is>(tpl)), ...);
}

int main() {
    MyStruct s{42, 3.14, 'c'};
    
    // 使用结构化绑定来创建一个tuple,这个tuple拥有MyStruct的所有成员
    auto [intValue, doubleValue, charValue] = s;
    
    // 将成员封装成tuple,并使用index_sequence来遍历每个元素
    auto tuple = std::make_tuple(intValue, doubleValue, charValue);
    processMembers(tuple, std::make_index_sequence<std::tuple_size<decltype(tuple)>::value>{});
    
    return 0;
}

在这个示例中,我们定义了一个MyStruct结构体,它包含三个不同类型的成员。我们使用结构化绑定来解构这个结构体并得到一个tuple。然后我们定义了一个printValue函数模板,它使用if constexpr来在编译期根据类型选择如何打印每个成员的值。

最后,在main函数中,我们使用结构化绑定来创建一个包含所有成员的tuple,并使用一个模板函数processMembers来遍历并处理这个tuple中的每个成员。我们使用std::index_sequence来生成一个编译期的索引序列,这允许我们在编译期展开对每个成员的处理。

虽然这个示例并没有直接遍历结构体的成员,它展示了如何使用结构化绑定来简化成员的访问并结合模板和constexpr来进行编译期计算。这种方法在处理已知结构体时非常有用,可以有效地利用C++20的新特性进行编译期优化。

但是,需要使用者在使用时每次都将结构体转换为结构化绑定,即:

	MyStruct s{42, 3.14, 'c'};
    // 使用结构化绑定来创建一个tuple,这个tuple拥有MyStruct的所有成员
    auto [intValue, doubleValue, charValue] = s;    
    // 将成员封装成tuple,并使用index_sequence来遍历每个元素
    auto tuple = std::make_tuple(intValue, doubleValue, charValue);
    processMembers(tuple, std::make_index_sequence<std::tuple_size<decltype(tuple)>::value>{});

这样依然很不优雅。

我们可以把结构化绑定的代码,通过某种形式转移到结构体声明中去,这样就能避免每次序列化和反序列化写结构化绑定的代码了:

#include <iostream>
#include <tuple>
#include <sstream>

#define TO_TUPLE(...) auto toTuple() { return std::tie(__VA_ARGS__); }

struct Person {
	std::string name;
	int age;
	TO_TUPLE(name, age)  // 使用宏来简化tuple的生成
};

template<typename T>
std::string serialize(T& data) {
	auto tuple = data.toTuple();
	std::ostringstream oss;
	std::apply([&oss](auto&&... args) {
		((oss << args << " "), ...);
		}, tuple);
	return oss.str();
}

template<typename T>
T deserialize(const std::string& s) {
	T data; // 创建一个新的T类型的实例
	auto tuple = data.toTuple(); // 获取成员的tuple
	std::istringstream iss(s);
	std::apply([&iss](auto&&... args) {
		((iss >> args), ...); // 读取每个成员
		}, tuple);
	return data; // 返回填充好的数据结构
}

int main() {
	Person p{ "Alice", 30 };
	std::string serialized = serialize(p);
	std::cout << "Serialized: " << serialized << std::endl;

	Person deserialized = deserialize<Person>(serialized);
	std::cout << "Deserialized: " << deserialized.name << ", " << deserialized.age << std::endl;
}

在这里插入图片描述
上面代码中,虽然还需要在结构体里声明TO_TUPLE,但是相比前面两个实现,在设计上已经非常友好了。
对于自定义类型,通过stringstream的重载即可支持序列化操作,不再冗述。

在现有的C++技术中,不依赖外部工具,也只能做到这一步。无法省略结构体的TO_TUPLE,而且无法可靠地检测出开发者是否存在漏写的情况。

实际上,上述代码在支持C++17的环境中即可正常运行,不需要C++20。

展望未来C++标准实现反射库

在C++ 26有关反射的提案,用提案中的方法,可以方便地遍历结构体的成员:

struct S { unsigned i:2, j:6; };
consteval auto member_number(int n) {
  if (n == 0) return ^S::i;
  else if (n == 1) return ^S::j;
}

int main() {
  S s{0, 0};
  s.[:member_number(1):] = 42;  // Same as: s.j = 42;
  s.[:member_number(5):] = 0;   // Error (member_number(5) is not a constant).
}

类似地,很容易将结构体的所有成员转换为元组,从而省略我们最终方法中的依赖在结构体声明TO_TUPLE的限制了。

结语

随着C++标准的不断进化,实现简洁又实用的序列化工具变得越来越简单。
目前我们还无法完全实现对结构体无侵入地实现反射,但是未来即将可以轻松实现。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

神经网络(四):UNet语义分割网络

文章目录 一、简介二、网络结构2.1编码器部分2.2解码器部分2.3完整代码 三、实战案例 一、简介 UNet网络是一种用于图像分割的卷积神经网络&#xff0c;其特点是采用了U型网络结构&#xff0c;因此称为UNet。该网络具有编码器和解码器结构&#xff0c;两种结构的功能如下&#…

Redis的数据类型常用命令

目录 前言 String字符串 常见命令 set get mget mset setnx incr incrby decr decyby append Hash哈希 常见命令 hset hget hexists hdel hkeys hvals hgetall hmget hlen hsetnx List 列表 常见命令 lpush lrange lpushx rpush rpushhx lpop…

postman下载安装和导入导出脚本一键执行

下载和安装 首先&#xff0c;下载并安装PostMan&#xff0c;请访问PostMan的官方下载网址&#xff1a;https://www.getpostman.com/downloads/ 下载所需的安装程序后&#xff0c;直接安装即可 第一次打开会要求登录账号密码&#xff0c;如果没有&#xff0c;直接关闭&#xf…

海报制作哪个软件好?建议试试这5个

2024年过得飞快&#xff0c;转眼间国庆佳节即将到来。 在这个举国欢庆的时刻&#xff0c;无论是商家还是个人&#xff0c;都希望通过海报来传递节日的喜悦和祝福。制作一张吸引人的海报&#xff0c;不仅能提升品牌形象&#xff0c;还能增强节日氛围。 那么&#xff0c;如何快…

【Python报错已解决】TypeError: can only concatenate str (not “int“) to str

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

【Java 问题】基础——Java 概述

Java 概述 1. 什么是 Java ?2. Java 语言有哪些特点3. JVM、JDK 和 JRE 有什么区别&#xff1f;4. 说说什么是跨平台性&#xff1f;原理是什么&#xff1f;5. 什么是字节码&#xff1f;采用字节码的好处是什么&#xff1f;6. 为什么说 Java 语言 "编译与解释并存"?…

汽车行业SAP全球模版导入方案【集团出海部署】

在汽车行业实施SAP系统是一个复杂且具挑战性的项目&#xff0c;涉及多个业务模块和跨部门协作。以下是一个汽车行业SAP实施的导入方案&#xff0c;包括关键步骤、模块选择、最佳实践和注意事项。 1. 项目启动及规划 项目启动 项目发起&#xff1a;确定项目范围、目标和业务需…

Spring源码-ConfigurationClassPostProcessor类解析spring相关注解

ConfigurationClassPostProcessor类的作用 此类是一个后置处理器的类&#xff0c;主要功能是参与BeanFactory的建造&#xff0c;主要功能如下 1、解析加了Configuration的配置类 2、解析ComponentScan扫描的包 3、解析ComponentScans扫描的包 4、解析Import注解 该类在springbo…

【原创教程】如何用西门子1500读写巴鲁夫RFID

实现的功能及应用的场合 通过使用RFID进行对托盘信息工件信息的追踪记忆&#xff0c;方便了解工件的状态内容。适用于流水线等场合。 硬件配置 巴鲁夫RFID 巴鲁夫RFID一套包含&#xff1a;RFID分析单元&#xff0c;RFID数据读写头&#xff0c;RFID数据载体。 ①RFID分析单…

ai写论文哪个平台好?分享4款ai论文写作平台软件

在当前的学术研究和论文写作领域&#xff0c;AI技术的应用已经成为一种趋势。通过智能算法和大数据分析&#xff0c;AI工具能够帮助学者和学生提高写作效率、优化内容结构&#xff0c;并确保论文的原创性和质量。以下是四款备受推荐的AI论文写作平台软件&#xff1a; 1. 千笔-…

Js基础

JS编写位置 将代码编写在html网页script标签 <script>// 弹出alert("test")// 控制台输出日志console.log("hello world")// 向网页输入内容&#xff0c;即往body中写内容document.write("write content")</script> 将代码编写在外部…

原腾讯云AI产品线项目经理李珊受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 原腾讯云AI产品线项目经理、资深项目管理专家李珊女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为&#xff1a;AI助力项目经理的决策支持系统。大会将于10月26-27日在北京举办…

生态布局再进一步!拓数派 PieCloudDB Database 与 openEuler 完成兼容互认证

随着信息技术的快速发展&#xff0c;国产化自主创新已成为国家战略的核心部分。拓数派自主研发的云原生虚拟数仓 PieCloudDB 与国产操作系统 openEuler 已完成相互兼容性测试&#xff0c;并获得 openEuler 技术测评证书。 目前&#xff0c;拓数派已成功与华为鲲鹏、麒麟软件、龙…

基于Java+Mysql实现的PC端图书管理系统软件

Library_system 图书管理系统。用Java实现的PC端软件。使用MySql作为DBMS操作本地数据库&#xff0c;用JDBC连接Java和数据库。实现图书管理系统的基本功能 项目介绍 该项目主要实现了图书管理系统几个主要的基本功能&#xff0c;做这小项目是为了简单学习数据库设计、包括E…

系统架构师-面向服务架构(SOA)全解

1、为什么需要SOA架构 1.1 系统集成问题 异构系统整合 例如&#xff0c;一个企业可能同时拥有用 Java 开发的企业资源规划&#xff08;ERP&#xff09;系统、用 C# 开发的客户关系管理&#xff08;CRM&#xff09;系统以及用 Python 开发的数据分析系统。通过 SOA&#xff0…

9月24日笔记

内网信息收集 本机基础信息收集 当通过web渗透或者其他方式活动服务器主机权限之后&#xff0c;需要以该主机作为跳板&#xff0c;对内网环境进行渗透&#xff0c;对于攻陷的第一台主机&#xff0c;其在内网中所处的网络位置、当前登录的用户、该用户有什么样的权限、其操作系…

微信小程序开发第八课

一 公告 1.1 微信小程序端 #js###const api require("../../config/settings.js") Page({data: {noticeList: [{title: 公告标题1,create_time: 2024-04-25,content: 公告内容描述1&#xff0c;公告内容描述1&#xff0c;公告内容描述1。, // 可以根据实际情况添加…

几个将ppt文件压缩变小的方法!

几个将ppt文件压缩变小的方法&#xff01;在构建集文字、图像、视频及数据表于一体的综合PPT演示文稿时&#xff0c;一个常见挑战是随着内容的不断丰富&#xff0c;文件体积也随之膨胀&#xff0c;这往往源于直接嵌入未经优化的多媒体资源及设计上的冗余元素&#xff0c;如繁复…

数字人实战第五天——Dinet 训练自己的数字人

一、简介 DINet 是一个形变修复网络&#xff0c;专门用于解决高分辨率人脸视觉配音中的难题。它的设计目的是为了提升视觉配音的保真度和细节丰富性&#xff0c;特别是在少样本学习的情境下&#xff0c;即在训练数据较少的情况下依然能够实现较好的配音效果。 DINet的技术实现…

html TAB切换按钮变色、自动生成table

<!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 1px solid #ccc; padding: 1px; marg…