C++序列化对象
需求
. 在写代码的过程中,经常会需要把代码层面的对象数据保存到文件,而这些数据会以各种格式存储.例如: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); // 反序列化
. 上面是比较理想的接口原型,序列化/反序列化各一个接口,脑补一下,这两个接口的实现应该是怎样的?最直接的实现是对每一种类型重载一个定义,例如:
// string
void Serialize(std::ostream & os, const std::string & val)
{
os.write(str.data(), str.size());
}
// vector<int>
void Serialize(std::ostream & os, const std::vector<int> & val)
{
os.write(str.data(), str.size() * sizeof(int));
}
// vector<string>
void Serialize(std::ostream & os, const std::vector<std::string> & val)
{
for (auto & str: val)
{
Serialize(os, str);
}
}
// 接口调用
std::string str;
std::vector<int> vecint;
std::vector<std::string> vecstr;
Serialize(os, str);
Serialize(os, vecint);
Serialize(os, vecstr);
. 从上面可以看出,接口统一,使用方便.但是对每一种类型都重载,要写的代码实在太多了,万一要序列化一个多层嵌套数组,会写的怀疑人生.借助C++强大的语言特性,这一切都可以一步到位.
// 可平凡复制
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())> &&
std::is_trivially_copyable_v<typename T::value_type>, int> N = 0>
void Serialize(std::ostream & os, const T & val)
{
unsigned int size = val.size();
os.write((const char *)&size, sizeof(size));
os.write((const char *)val.data(), size * sizeof(typename T::value_type));
}
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())> &&
!std::is_trivially_copyable_v<typename T::value_type>, 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); }
}
// 可平凡复制
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())> &&
std::is_trivially_copyable_v<typename T::value_type>, int> N = 0>
void Deserialize(std::istream & is, T & val)
{
unsigned int size = 0;
is.read((char *)&size, sizeof(unsigned int));
val.resize(size);
is.read((char *)val.data(), size * sizeof(typename T::value_type));
}
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())> &&
!std::is_trivially_copyable_v<typename T::value_type>, 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机制,可保证类型安全又具备可扩展性.
C++序列化对象的更多相关文章
- Android使用HttpURLConnection通过POST方式发送java序列化对象
使用HttpURLConnection类不仅可以向WebService发送字符串,还可以发送序列化的java对象,实现Android手机和服务器之间的数据交互. Android端代码: public ...
- 序列化对象为xml字符串
/// <summary> /// 序列化对象为xml字符串 /// </summary> /// <param name="obj" ...
- Serializable序列化对象
Serializable序列化对象发送: Intent intent = new Intent(); intent.setClass(mContext, HomeDetailReportActivit ...
- C# 使用XML序列化对象(二)
在C# 使用XML序列化对象(一)中描述了使用XML序列化对象的最简单的实现. 现在我们来看看稍微复杂一点的情况: 现有两个类:A和B,B是A的派生类,如下所示: public class A { p ...
- Android 使用Parcelable序列化对象
转:http://ipjmc.iteye.com/blog/1314145 Android序列化对象主要有两种方法,实现Serializable接口.或者实现Parcelable接口.实现 ...
- Android中序列化对象到XMl 和 XML反序列化为对象
package com.example.xmloperation; import java.io.File; import java.io.FileOutputStream; import java. ...
- 2进制,16进制,BCD,ascii,序列化对象相互转换
public final static char[] BToA = "0123456789abcdef".toCharArray() ; 1.16进制字符串转为字节数组 /** * ...
- c#xml序列化对象,xml标记都缩写了
最近最后一个接口,他们的格式很严格必须是如下格式 <message> <age>20</age> <name>张三</name> </ ...
- Gson序列化对象时排除字段
import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; /** *Gson序列化对象排除属性 ...
- java序列化对象 插入、查询、更新到数据库
java序列化对象 插入.查询.更新到数据库 : 实现代码例如以下: import java.io.ByteArrayInputStream; import java.io.ByteArrayOutp ...
随机推荐
- Python知识点入门笔记——特色数据类型(集合)
集合是一种不重复的无序集 集合用花括号来定义{} 集合和字典一样,里面的顺序是无序的,{1,2,3}和{3,2,1}是相等的 集合的元素不可重复,也就是说{1,2,2,3}是不存在的,应该写为{1,2 ...
- GBK UTF8 GB2132
GBK就是在保存你的帖子的时候,一个汉字占用两个字节,外国人看会出现乱码,为此我中华为自己汉字编码而形成之解决方案. UTF8就是在保存你的帖子的时候,一个汉字占用3个字节.但是外国人看的话不会乱码. ...
- C++从键盘读入数组并存储
C++从键盘读取任意长度的数组,现总结如下: //读取指定长度的数组 int main() { int n = 0; cin >> n; vector<int> p(n); f ...
- 遗传算法 | Java版GA_TSP (2)
嗯哼,上一篇博客中用Java实现了遗传算法求解TSP(Java版GA_TSP(我的第一个Java程序)),但明显求解效果不太好,都没太好意思贴出具体的结果,今天捣腾了下,对算法做了一些小改进,求解效果 ...
- 17,基于scrapy-redis两种形式的分布式爬虫
redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls ...
- mysql不能登陆
前些天还正常运行的mysql,不知怎么就不能登陆了.错误提示为 :ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (1 ...
- MySQL基础9-主键约束、外键约束、等值连接查询、一对一和多对多关系
1.主键约束和外键约束 外键约束 * 外键必须是另一表的主键的值(外键要引用主键!) * 外键可以重复 * 外键可以为空 * 一张表中可以有多个外键! 概念模型在数据库中成为表 数据库表中的多对一关系 ...
- Ping过程&ICMP
1.ICMP(Internet控制消息协议) ICMP=Internet Control Message Protocol 它是TCP/IP协议族的一个子协议 作用:用于在IP主机.路由之间传递控制消 ...
- Android 使用intent传递返回值:startActivityForResult()与onActivityResult()与setResult()参数分析,activity带参数的返回
在一个父Activity通过intent跳转至多个不同子Activity上去,当子模块的代码执行完毕后再次返回父页面,将子activity中得到的数据显示在主界面/完成的数据交给父Activity处理 ...
- Python学习-day16-DOM
文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...