C++学习 优雅的实现对象到文件的序列化/反序列化 关键字serialize

需要使用到序列化场景的需求

  • 在写代码的过程中,经常会需要把代码层面的对象数据保存到文件,而这些数据会以各种格式存储.例如:json,xml,二进制等等.二进制,相比json,xml格式,直接把字节copy到硬盘,所以这实现起来相对容易.

例子

  • 下面是一个简单的三维向量结构体,如何把它序列化到文件呢?

struct Vec3 {float x;float y;float z;
}
Vec3 v; 
v.x = 1.0f; 
v.y = 2.0f; 
v.z = 3.0f; 
os.write((const char *)&v, sizeof(Vec3));
  • 上述是序列化Vec3对象数据到文件的代码,非常直接.它的内存布局是3个浮点型变量紧凑排列,要把它存储到硬盘,只要从头到尾按字节拷贝即可.但是,在实际开发中,要序列化的对象不可能全部都是内存紧凑排列的,例如STL容器.
std::vector<Vec3> vec;
  • 如果将容器变量从头到尾拷贝到文件,必然会出现错误.因为容器内部通过一个指针来访问存储的对象,而直接拷贝这个容器,只会把指针拷贝,指针指向的数据却丢失了.但是,容器提供了一个可以直接访问指针指向数据的接口,可以通过这个接口得到数据然后直接拷贝.
os.write((const char *)&vec, vec.size() * sizeof(Vec3));        //  错误, 仅拷贝指针
os.write((const char *)vec.data(), vec.size() * sizeof(Vec3));  //  正确, 数据被完全拷贝
  • 通过这个方法就可以得到正确的拷贝结果了.通常,好的做法是将序列化和反序列化封装成接口,以便于使用,如何封装接口,就是这篇文章的主题.从上述两个例子可以发现,对于单体对象和数组对象,编写的代码是不一样的,单体对象直接拷贝,数组对象需要通过 .data() 取得数据地址再进行拷贝.而考虑到还有嵌套数组像 std::vector<std::vector<Vec3>>.对于嵌套数组序列化的代码可能如下:
std::vector<std::vector<Vec3>> vec2;
for (auto & vec: vec2)
{os.write((const char *)vec.data(), vec.size() * sizeof(Vec3));
}
  • 可以发现,对嵌套数组对象的序列化代码跟上述2种对象又不一样,考虑到还有N层嵌套的数组对象.此外,在C++中有一个 可平凡复制的概念 ,通俗的说,就是可以直接按字节拷贝的结构称之为 可平凡复制 ,上述的 Vec3 则是一个可平凡复制结构,而STL容器则不是可平凡复制结构,除此之外还有更多不可平凡复制且非容器的结构,故此,如果要封装接口,除了区分单体对象和数组对象,还要区分可平凡复制和不可平凡复制.
void Serialize(std::ostream & os,   const Type & val);  //  序列化
void Deserialize(std::istream & is,       Type & val);  //  反序列化
//  POD
template <class T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0>
void Serialize(std::ostream & os, const T & val)
{os.write((const char *)&val, sizeof(T));
}//  容器
template <class T, typename std::enable_if_t<std::is_same_v<typename T::iterator, decltype(std::declval<T>().begin())> &&std::is_same_v<typename T::iterator, decltype(std::declval<T>().end())>, int> N = 0>void Serialize(std::ostream & os, const T & val)
{unsigned int size = val.size();os.write((const char *)&size, sizeof(size));for (auto & v : val) { Serialize(os, v); }
}//  POD
template <class T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0>
void Deserialize(std::istream & is, T & val)
{is.read((char *)&val, sizeof(T));
}//  容器
template <class T, typename std::enable_if_t<std::is_same_v<typename T::iterator, decltype(std::declval<T>().begin())> &&std::is_same_v<typename T::iterator, decltype(std::declval<T>().end())>, int> N = 0>void Deserialize(std::istream & is, T & val)
{unsigned int size = 0;is.read((char *)&size, sizeof(unsigned int));val.resize(size);for (auto & v : val) { Deserialize(is, v); }
}
  • 以上实现可序列化任意 可平凡拷贝 结构,并且也可序列化任意嵌套层数的STL风格数组.而对于 不可平凡复制 结构,只需要针对该结构重载即可.借助C++强大的类型推导机制和SFINEA机制,可保证类型安全又具备可扩展性.

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

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

相关文章

C++代码注释详解

常用注释语法 注释写在对应的函数或变量前面。JavaDoc类型的多行注释风格如下&#xff1a; /** * 这里为注释. */ 一般注释中有简要注释和详细注释&#xff0c;简要注释有多种标识方式&#xff0c;这里推荐使用brief命令强制说明&#xff0c;例如&#xff1a;/** * brief 这里…

段错误:SIGSEGV

SIGSEGV是在访问内存时发生的错误&#xff0c;它属于内存管理的范畴 SIGSEGV是一个用户态的概念&#xff0c;是操作系统在用户态程序错误访问内存时所做出的处理。 当用户态程序访问&#xff08;访问表示读、写或执行&#xff09;不允许访问的内存时&#xff0c;产生SIGSEGV。 …

web3 0.2.x 和 1.x.x版本之间的差异

版本差异 单位转换 0.2.x web3.fromWei(13144321,ether) 1.x.x web3.utils.fromWei(13144321,ether)1.0以后的版本使用了大量的Promise&#xff0c;可以结合async/await使用&#xff0c;而0.20版本只支持回调函数

如何提高阅读源码的能力并且手撕源码

怎么有效的手撕代码呢&#xff1f; 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo

并发和并行的区别简单介绍

并发和并行 并发是关于正确有效地控制对共享资源的访问 同时完成多个任务。在开始处理其他任务之前&#xff0c;当前任务不需要完成。并发解决了阻塞发生的问题。当任务无法进一步执行&#xff0c;直到外部环境发生变化时才会继续执行。最常见的例子是I/O&#xff0c;其中任务…

手撕源码 alloc

怎么有效的手撕代码呢&#xff1f; gnu gcc 2.9 的 内存池 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo 第三步&#xff1a; 第五步: // 这个头文件包含一个模板类 allocator&#xff0c;用于管理内存的分配、…

Algorand的共识协议及其核心的优势

Algorand 设计的初衷 Algorand 想解决的核心问题是&#xff1a;去中心化网络中低延时&#xff08;Latency&#xff09;和高置信度&#xff08;Confidence&#xff09;之间的矛盾。其中&#xff0c;延时指从发起交易到确认交易所需要的时间&#xff1b;置信度指的是发出的交易不…

手撕源码 SQL解析器 sqlparser

怎么有效的手撕代码呢&#xff1f; 源代码&#xff1a;https://github.com/hyrise/sql-parser 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo

针对Algorand所使用的密码相关技术细节进行介绍

关键概念 VRF: 可验证随机函数。简单来说是&#xff1a;vrf,Proof VRF(sk,seed)&#xff0c;sk为私钥&#xff0c;seed为随机种子&#xff1b;通过Verify(proof,pk,seed)验证vrf的合法性。cryptographic sorition: 根据用户本轮的VRF值&#xff0c;自身的权重以及公开的区块链…

内存池的实现1 :重载

#ifndef KSTD_ALLOCATOR_H_ #define KSTD_ALLOCATOR_H_// 这个头文件包含一个模板类 allocator&#xff0c;用于管理内存的分配、释放&#xff0c;对象的构造、析构 // 暂不支持标准库容器 todo::支持萃取#include <new> // placement new #include <cstddef>…

对于Algorand的介绍

介绍 Algorand具有能耗低、效率高、民主化、分叉概率极低、可拓展性好等优点&#xff0c;旨在解决现有区块链项目存在的“不可能三角”&#xff08;高度可扩展的、安全的、去中心化&#xff09;问题。Algorand由MIT教授、图灵奖得主Silvio Micali发起&#xff0c;拥有MIT区块链…

内存池的实现2 类专用的内存适配器

B类增加了嵌入指针 #include<new> #include<ctime> #include<iostream> #include<cstdio> class A { public:A() {printf("next%p\n", next);};static void* operator new(size_t size);static void operator delete(void* phead);static i…

C++学习 高级编程

C 文件和流 到目前为止&#xff0c;目前使用最为广泛的是 iostream 标准库&#xff0c;它提供了 cin 和 cout 方法分别用于从标准输入读取流和向标准输出写入流。以下将介绍从文件读取流和向文件写入流。这就需要用到 C 中另一个标准库 fstream&#xff0c;它定义了三个新的数…

内存池的实现3 固定大小的allocator单线程内存配置器

如果我们想使内存管理器用于其他大小不同的类该怎么办呢&#xff1f;为每一个类重复管理逻辑显然是对开发时间的不必要浪费。如果我们看一下前面内存管理器的实现&#xff0c;就会明显地看出内存管理逻辑实际上独立于特定的类 有关的是对象的大小一这是内存池模板实现的良好候选…

C++中文版本primer 第二章变量和基本类型 学习笔记

2.2变量 2.2.1 变量定义 列表初始化 定义一个名字为units_sold的int变量并初始化为0 int units_sold 0; int units_sold {0}; int units_sold{0}; int units_sold(0); C11 用花括号来初始化变量&#xff0c;上面这个步骤也称之为列表初始化。这种初始化有一个重要的特点&…

内存池中的嵌入式指针

嵌入式指针 可以union改struct 内存分配后 next指针就没用了 直接作为数据空间比较省内存 因为对指针指向的内存存储的时候 编译器是不管你是什么类型的 &#xff0c;这里有道练习题可以对指针的概念稍微理解一下&#xff1a; #include <iostream> using std::cout; us…

C++ 标准程序库std::string 详解

现在一般不再使用传统的char*而选用C标准程序库中的string类&#xff0c;是因为string标准程序和char*比较起来&#xff0c;不必担心内存是否足够、字符串长度等等&#xff0c;而且作为一个类出现&#xff0c;集成的操作函数足以完成大多数情况下(甚至是100%)的需要。比如&…

内存池的实现4 alloc内存池

alloc 内存池 优点: &#xff1a;本质是定长内存池的改进&#xff0c;分配和释放的效率高。可以解决一定长度内存分配的问题。 缺点 &#xff1a;存在内碎片的问题&#xff0c;且将一块大内存切小以后&#xff0c;申请大内存无法使用&#xff0c;别的FreeList挂了很多空闲的内存…

C++primer第15章节详解面向对象程序设计

前言 面向程序设计基于三个基本概念&#xff1a;数据抽象、继承和动态绑定。继承和动态绑定可以使得程序定义与其他类相似但是不完全相同的类&#xff1b;使用彼此相似的类编写程序时候&#xff0c;可以在一定程度上忽略掉他们的区别。 OOP概述 oop&#xff08;面向程序的设…