【3rdparty】cereal简单使用

获取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的数据结构,处理方式才可以实现。

猜你喜欢

转载自blog.csdn.net/gx864102252/article/details/81271550