获取Cereal
在Github上可以获取最新版本的cereal:
https://github.com/USCiLab/cereal
使用Cereal
只需配置头文件路径即可。
序列化基础知识
cereal支持二进制、XML和Json序列化。读写操作是基于C++的std::ostream和std::istream,也就是操作的对象可以是文件、内存、或者标准的输入输出。
特别注意,使用cereal的首选方式是RAII方式。 cereal只能保证存储类在销毁时完成内容的刷新,因此一些存储类(例如XML)在销毁之前不会输出任何内容。例子如下:
{
ofstream out("out.xml");
// depending on the archive type, data may be
// output to the stream as it is serialized, or
// only on destruction
cereal::XMLOutputArchive archive(out);
archive(some_data, more_data, data_galore);
} // when archive goes out of scope it is guaranteed to have flushed its contents to its stream
序列化二进制数据
二进制序列化需要包含头文件<cereal/archives/binary.hpp>
。二进制序列化生成bit级的紧凑数据格式,并且不是人类可读的。二进制序列化方式只会序列化变量的值,而不是“变量名-值”这种方式。
二进制序列化不保证字节序。如果您的数据将在小端和大端机器上读取,您应该使用<cereal/archives/portable_binary.hpp>
,它跟踪保存和加载机器的字节顺序并适当地转换数据。它的开销略高于常规二进制序列化方式。
使用二进制存档和文件流(std::fstream)时,请记住在构造流时指定二进制标志为(std::ios::binary
)。这可以防止流将您的数据篡改为ASCII字符。
XML
可以通过包含使用XML的头文件<cereal/archives/xml.hpp>
。XML是一种人类可读的格式,不应在序列化数据大小至关重要的情况下使用。与二进制存档不同,二进制存档在调用序列化函数时递增地输出其数据,XML存档在内存中构建一个树,仅在销毁存档时输出它。
与二进制文件不同,XML存档将利用“变量-值”对为其输出提供人类可读的名称。如果未提供“变量-值”键值对,它将自动生成枚举名称。XML归档不需要为可变大小的容器输出大小元数据,因为在加载数据时可以查询节点的子节点数。这意味着可以在加载之前手动将额外数据添加到XML中。
示例如下:
//帮助输出函数
template<class T>
ostream& operator << (ostream& out, std::vector<T>& obj)
{
for (auto value : obj)
{
out << value << " ";
}
out << endl;
return out;
}
//将数据写入xml文件
ofstream out;
{
out.open("./out.xml", ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
cereal::XMLOutputArchive archive(out);
string str = "this string";
std::vector<int> vec = { 1,2,3,4,5 };
archive(CEREAL_NVP(vec), cereal::make_nvp("string_type_name", str));
}
out.close();
//从xml文件读取数据
ifstream in;
string str = "";
std::vector<int> vec = {};
{
in.open("./out.xml", ios::in);
cereal::XMLInputArchive archive(in);
archive(vec, str);
}
in.close();
cout << vec << " "<<str << endl;
我们可以看到目录下生成了一个xml文件,并且控制台输出为:
可以看到正确的吧数据写入xml,并且可以正确读取。
该xml文件中,vec部分的size属性为“dynamic”的标签,Cereal将其标记为动态模式,我们可以手动往下加入其他子项。
示例读取xml文件:
测试输出为:
XML可以通过使用属性标签的方式输出完整的类型信息,并且还可以控制浮点数的输出精度。如果你要保证浮点数的精度,需要手动设置浮点数的精度(float是10,double是20,long double是40)。默认情况下,Cereal会使用最大可能的保证double的输出精度。
乱序加载XML
所有序列化的默认行为是按照出现的顺序依次加载数据。XML(和JSON)序列化支持乱序加载,这意味着您可以利用“变量-值”对以不同于XML文件中显示的顺序加载数据。
当Cereal检测到您正在使用NVP从XML存档加载数据时,它会检查该名称是否与下一个预期(按顺序)名称匹配。如果它们不匹配,Cereal将在当前节点级别内搜索提供的名称。如果找不到名称,则会抛出异常
。一旦谷物找到并加载名称,它将从该位置顺序进行,直到强行通过NVP搜索另一个名称。
示例,如下面的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>
//code
struct MyData
{
bool b;
double d;
template <class Archive>
void serialize(Archive & ar)
{
ar(b, d);
}
friend ostream& operator << (ostream& out, MyData& obj);
};
ostream& operator << (ostream& out, MyData& obj)
{
out << "Bool:"<<std::boolalpha<< obj.b <<" Double:"<< obj.d <<endl;
return out;
}
//测试代码
int i1, i2, i3, i4, i5;
MyData md;
{
ifstream in("test.xml");
cereal::XMLInputArchive archive(in);
archive(cereal::make_nvp("myData", md));
archive(i5); // 寻找下一个匹配的节点
archive(cereal::make_nvp("var1", i1)); //从新开始查询节点名称为var1
//archive(i2, i3, i4); //error节点匹配不正确
archive(i2, i3); //继续查询接下来的节点
//archive(i4); //error节点匹配不正确 (myData)
}
cout << md << endl;
cout << i5 << endl;
cout << i1 << endl;
cout << i2 << endl;
cout << i3 << endl;
//cout << i4 << endl;
cereal默认行为是按顺序加载数据在存档中显示。如果您使用NVP加载无序的东西,cereal将立即从您导致它跳转到的节点开始恢复此行为。
二进制输出
XML序列化还支持显示二进制输入输出,它将数据编码为base64格式字符串:
ofstream out;
{
out.open("./out.xml", ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
cereal::XMLOutputArchive archive(out);
int value = 4;
archive.saveBinaryValue(&value, sizeof(int));
}
out.close();
ifstream in;
int value = 0;
{
in.open("./out.xml", ios::in);
cereal::XMLInputArchive archive(in);
archive.loadBinaryValue(&value, sizeof(int));
}
in.close();
cout << "value :" << value << endl;
结果输出:4
Json
可以通过包含使用JSON序列化头文件<cereal/archives/json.hpp>
。JSON是一种人类可读的格式,序列化后的数据不易过大。JSON存档与XML存档非常相似,它们将在可用时利用“变量-值”对,并在不存在时自动生成名称。
示例如下:
ofstream out;
{
out.open("./out.json", ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
cereal::JSONOutputArchive archive(out);
bool arr[] = { true, false };
std::vector<int> vec = { 1, 2, 3, 4, 5 };
std::array<int, 3> obj = { 10, 20, 30 };
archive(cereal::make_nvp("vector", vec), CEREAL_NVP(arr), CEREAL_NVP(obj));
}
out.close();
ifstream in;
bool arr[] = {0 };
std::vector<int> vec = { };
std::array<int, 3> obj = {};
{
in.open("./out.json", ios::in);
cereal::JSONInputArchive archive(in);
//乱序读取
archive(CEREAL_NVP(arr));
archive(cereal::make_nvp("vector", vec));
archive(CEREAL_NVP(obj));
}
in.close();
看Json文件如下:
并且Json也支持乱序读取如上面代码。
注意动态大小的容器(例如std::vector)如何序列化为JSON数组,而固定大小的容器(例如std::array)被序列化为JSON对象。这种区别很重要,因为它允许您通过将元素插入到数组中来将数据插入JSON文件中的可变大小的容器,而您无法将新数据插入到对象中。您仍然可以手动编辑对象中的值,但不能追加或从中扣除数据。
使用JSON序列化数字类型: 所有数字类型都被序列化为数字,但大型类型除外,例如long long,unsigned long long和long double,它们被序列化为字符串。在JSON中,数字不会有周围的引号,而较大类型的字符串表示将用双引号括起来。
基本的使用方式就介绍到这了,想要使用自定义的格式,还是需要仔细研究cereal的数据结构,处理方式才可以实现。