本文翻译自:
http://uscilab.github.io/cereal/serialization_archives.html
Cereal支持二进制、XML和JSON三种格式。
TLDR版本
Cereal支持二进制、XML和JSON三种格式的读写操作,在使用时包含对应的头文件,例如’cereal/archives/xxxx.hpp’。如果要使用多态,则需要在多态声明之前包含对应的头文件。
基本知识
存储格式决定了数据是如何被输出和读取。不过大多数情况下,我们并不需要知道某种存储格式的内部工作原理,因为使用时Cereal已经做了很好的封装。但是为了更加个性化,Cereal也只是为不同的序列化格式设置对应的序列化函数。
Cereal的读写操作是基于C++的std::ostream和std::istream。这意味着,操作对象可以是文件、内存流,甚至标准的输入输出。
值得注意的是,Cereal的工作方式是RAII规范,即只有在存储类被销毁时,才能完全保证完全输出。换句话说,如果存储类没有被销毁,甚至可能存在没有输出任何信息。因此,合理使用Cereal的方式是使用大括号来规定存储类的生存范围,保证存储类在合适的时候自动销毁。
#include <cereal/archives/xml.hpp>
#include <fstream>
{
std::ofstream file( "out.xml" );
cereal::XMLOutputArchive archive( file );
archive( some_data, more_data, data_galore );
} // 当程序运行到此处时,Cereal的存储类就会自动销毁,从而完成序列化操作
Cereal在存储设计时,并没有考虑长期的兼容性,而是将这个兼容性工作移交给开发者自行处理。一种比较合理的方式时,尽量保证在读写时都使用同一个版本的Cereal。
好消息时,Cereal和Boost一样,也支持在序列化内容中添加版本信息,相关信息请参考“Cereal序列化函数”。
高级模式:多态
关于多态模式下序列化的详细信息,可以参考“Cereal高级模式之多态”。此处简单说一下,即如果序列化一个多态派生类,必须在声明之前包含Cereal头文件。
二进制序列化
二进制序列化需要包含cereal/archives/binary.hpp。二进制类型是一种bit级压缩数据格式,因此无法直接供人阅读。二进制序列化是Cereal中最快的一种序列化方式,在序列化时只序列化变量的值,而不是“变量名-值”这种方式。
通常情况下,二进制序列化并不保证字节顺序。如果你需要保证在不同的平台上都能正常工作,则需要包含cereal/archives/portable_binary.hpp,从而保证字节顺序的一致性,但是这样会稍微降低性能。
当使用二进制序列化和文件流(std::fstream)时,记住使用二进制flag(std::ios::binary)构造流,这样能够保证流使用ASCII读写数据。
XML序列化
XML序列化需要包含cereal/archives/xml.hpp。XML序列化是一种可供人阅读的格式,并且在序列化对象数据量较大时最好不要使用该格式。与二进制序列化逐步输出内容不一样的是,XML序列化会在内存中维护一个树形结构,并且仅在其析构时输出内容。
XML序列化使用了“变量名-值”模式,对于人来说有非常好的可阅读性。如果不设置“变量名-值”,XML序列化会自动生成一个数字的变量名。XML序列化在序列化时不需要元数据来设置变量的大小,因为在反序列化时会自动查询数据大小。这意味着在反序列化之前,可用手动修改XML文件来达到需要的目的。
下文中给出一个XML序列化的示例:
代码
#include <iostream>
#include <cereal/archives/xml.hpp>
#include <cereal/types/vector.hpp>
int main()
{
cereal::XMLOutputArchive archive( std::cout );
bool arr[] = {true, false};
std::vector<int> vec = {1, 2, 3, 4, 5};
archive( CEREAL_NVP(vec),
arr );
}
结果
<?xml version="1.0"?>
<cereal>
<vec size="dynamic"> <!-- 注意此处并没有设置list的大小,Cereal自动标记为动态模式 -->
<value0>1</value0> <!-- 如果没有使用NVP设置,变量名会自动设置 -->
<value1>2</value1>
<value2>3</value2>
<value3>4</value3>
<value4>5</value4>
<!-- 此处可以插入新的数据,反序列化时也可以正常读取 -->
</vec>
<value0> <!-- 注意,由于数组的大小是固定的,所以此处没有大小数据 -->
<value0>true</value0>
<value1>false</value1>
</value0>
</cereal>
如果你一定要手动编辑XML文件,需要注意新添加的数据是合法的,否则在反序列化时会报错,并且只能在dynamic标签下添加数据。
XML可以通过使用属性标签的方式输出完整的类型信息,并且还可以控制浮点数的输出精度。进一步说明,如果你要保证浮点数的精度,需要手工设置浮点数的输出精度(float是10,double是20,long double是40)。默认情况下,Cereal会使用最大可能的保证double的输出精度。
XML序列化依赖了RapidXML。
乱序反序列化
默认情况下,反序列化时都是按照存储的顺序进行数据读取,但是在XML(也包括JSON)都支持乱序反序列化。乱序的依据是之前提到的“变量名-值”,可以根据变量名来进行序列化。
当使用NVP宏进行反序列化时,Cereal会检查下一个按顺序待序列化内容的命名是否符合当前变量名。如果变量名不相同,Cereal会在剩下的节点中搜索该变量名。如果没找到变量名,就会抛出一个异常。反之如果找到了,Cereal即将该值反序列化,并从找到的节点开始,继续后续处理。
考虑如下的XML文件:
<?xml version="1.0"?>
<cereal>
<var1>4</var1>
<value0>32</value0>
<value1>64</value1>
<myData>
<value0>true</value0>
<another>3.24</another>
</myData>
<value2>128</value2>
</cereal>
下文中是如何乱序反序列化的代码:
#include <cereal/archives/xml.hpp>
#include <iostream>
struct MyData
{
bool b;
double d;
template <class Archive>
void serialize( Archive & ar )
{
ar( b, d );
}
};
int main()
{
int i1, i2, i3, i4;
MyData md;
{
std::ifstream is("data.xml");
cereal::XMLInputArchive ar(is);
// 提前反序列化myData
ar( cereal::make_nvp("myData", md));
//从找到的节点往后反序列化
ar( i4 );
ar( cereal::make_nvp("var1", i1) );
ar( i2, i3 );
// 再次读取到myData这个节点,由于变量名不存在抛出异常
ar( cereal::make_nvp("doesNotExist", i1) );
}
return 0;
}
注意:Cereal的默认反序列化顺序是按顺序操作。如果你使用了NVP宏进行乱序反序列化,那么Cereal将会从找到的节点开始继续往后进行反序列化!
二进制输出
XML也支持通过将数据编码成string,从而二进制的输入输出。下文中的代码显示了这种功能。
int main()
{
cereal::XMLOutputArchive archive( std::cout );
int arr[] = {-1, 95, 3};
archive.saveBinaryValue( arr, sizeof(int) * 3, "some_optional_name" );
}
输出如下:
<?xml version="1.0"?>
<cereal>
<some_optional_name>/18AAAADAAAA</some_optional_name>
</cereal>
相应使用loadBinaryValue进行读取。
JSON序列化
使用JSON进行序列化时只需要包含cereal/archives/json.hpp即可。JSON格式也是一种可供人阅读的格式。Cereal官方建议不要使用JSON格式序列化重要的数据。JSON和XML非常相似,也可以使用“变量名-值”的方式进行序列化,默认是随机生成变量名。下文中是一个Cereal生成JSON文档的代码,和XML非常类似。
#include <iostream>
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>
int main()
{
cereal::JSONOutputArchive archive( std::cout );
bool arr[] = {true, false};
std::vector<int> vec = {1, 2, 3, 4, 5};
archive( CEREAL_NVP(vec),
arr );
}
输入结果如下:
{
"vec": [
1,
2,
3,
4,
5
],
"value0": {
"value0": true,
"value1": false
}
}
可以发现,动态容器(例如std::vector)被序列化成了JSON数组,固定容器(例如std::array)被序列化成了JSON对象。这一点非常重要!当需要手动编写的JSON文件时,一定要注意上述规则。
在JSON中,除了long long,unsigned long long,long double被序列化成string,其他所有的数据都是序列化成数字。JSON中数字没有双引号包含,而string则会使用双引号包含。
JSON序列化依赖了RapidJson。
乱序反序列化
JSON序列化也支持乱序反序列化,具体操作和XML的类似,不再 赘述。
二进制输出
此处也和XML类似,所使用的函数也是saveBinaryValue和loadBinaryValue。
更多序列化类型
对于Cereal来说,添加其他类型的序列化类型是非常简单的。但是此处我就不翻译了,有点复杂~~,如果想了解更多信息,请参考原文。