是否可以对C ++中的类进行序列化和反序列化?


138

是否可以对C ++中的类进行序列化和反序列化?

我已经使用Java 3年了,在这种语言中,序列化/反序列化是相当琐碎的。C ++是否具有类似的功能?是否有处理序列化的本机库?

一个例子会有所帮助。


2
不确定“ native”是什么意思,是本地C ++(例如Boost.Serialization)吗?您是说只使用C ++标准库吗?你还有什么意思吗
jwfearn

1
我的意思是“不是外部软件库”。抱歉,我的英语不太好:S。我来自阿根廷
和Agusti-N

3
没有本机序列化对象的方法(您仍然可以从POD中转储二进制数据,但不会获得所需的内容)。尽管如此,Boost虽然不是“内部库”,但却是您应该考虑添加到编译器中的第一个外部库。Boost具有STL质量(即Top Gun C ++)
paercebal

Answers:


95

Boost::serialization库处理这个相当典雅。我已经在几个项目中使用过它。这里有一个例子程序,说明如何使用它,在这里

唯一的本地方法是使用流。这实际上是Boost::serialization库的全部工作,它通过设置框架将对象写为类似文本的格式并从相同格式读取它们来扩展stream方法。

对于内置类型,或您自己的类型operator<<operator>>正确定义的类型,这非常简单;有关更多信息,请参见C ++常见问题解答


在我看来,boost :: serialization需要调用者跟踪对象的写入和读取顺序。那是对的吗?因此,如果在程序版本之间写入两个字段的顺序发生了变化,那么我们将具有不兼容性。这是正确的吗?
Agnel Kurian

1
那可能是由于序列化功能,而不是Boost :: serialization代码本身。
极客头球2013年

1
@ 0xDEADBEEF:这可能是在使用binary_(i | o)归档时发生的,它引入了其他“问题”,例如字节序。尝试使用text_(i | o)存档,它与平台无关。
Ela782 2014年

2
特定的框架/库解决方案不应成为公认的答案。
Andrea

3
@Andrea:Boost库是一个特例。在C ++ 11最终定稿之前,几乎不可能没有它而编写现代C ++代码,因此它比一个单独的库更接近于辅助STL。
极客头

52

我意识到这是一篇很旧的文章,但这是搜索时出现的第一篇文章c++ serialization

我鼓励任何有权使用C ++ 11的人看一看谷类食品,即用于序列化的仅C ++ 11标头库,支持开箱即用的二进制,JSON和XML。谷物的设计易于扩展和使用,语法与Boost类似。


4
谷类食品的好处是,与boost不同,它具有最少的元数据(几乎没有)。当每次打开存档时,boost :: serialization变得非常烦人,它将存档的lib版本写入流中,这使得无法追加到文件中。
Cyber​​Snoopy 2014年

@Cyber​​Snoopy-创建归档文件时,有一个用于禁止此功能的标志-当然,在读取归档文件时,您也必须记住它。
罗伯特·拉米

16

升压是一个很好的建议。但是,如果您想自己动手,这并不难。

基本上,您只需要一种构建对象图并将其输出为某种结构化存储格式(JSON,XML,YAML等)的方法。建立图形就像使用标记递归体面对象算法然后输出所有标记对象一样简单。

我写了一篇文章,描述了基本的(但仍然功能强大)序列化系统。您可能会发现它很有趣:使用SQLite作为磁盘文件格式,第2部分


14

就“内置”库而言,<<>>已专门为序列化保留。

您应该重写<<以将您的对象输出到某个序列化上下文(通常是iostream),并>>从该上下文中读回数据。每个对象负责输出其聚合的子对象。

只要您的对象图不包含循环,此方法就可以正常工作。

如果是这样,那么您将不得不使用一个库来处理这些循环。


3
当然,这是不对的……实现的<<运算符用于打印对象的人类可读文本表示形式,而这通常不是序列化所需的内容。
einpoklum 2014年

1
@einpoklum而不是<<为泛型ostream定义,请尝试为文件流定义它。
致癌物质

1
@Carcigenicate:采用人类可读文本的日志文件是文件流。
einpoklum 2015年

1
@einpoklum我不太清楚你的意思。不过弗兰克(Frank)的权利,这些运算符可用于序列化。我只是定义它们以对向量进行序列化/反序列化。
Carcigenicate

2
我认为这里有个难题:“您应该重写<<以将您的对象输出到某个序列化上下文……每个对象都负责输出它的……” —问题在于如何避免必须为每个对象繁琐地写出来:语言或图书馆有帮​​助吗?
ShreevatsaR

14

我建议使用Google 协议缓冲区。我有机会在一个新项目上测试库,并且非常易于使用。该库针对性能进行了大量优化。

Protobuf与此处提到的其他序列化解决方案不同,因为Protobuf不会序列化您的对象,而是为根据您的规范进行序列化的对象生成代码。


2
您是否有使用此序列化大小约为10-50MB的对象的经验?文档似乎说协议缓冲区最适合大小约为MB的对象。
Agnel Kurian

我发布了自己的lib,尚未使用流,因此它确实适用于小事情: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty


4

您可以检查amef协议,例如amef中的C ++编码示例,

    //Create a new AMEF object
    AMEFObject *object = new AMEFObject();

    //Add a child string object
    object->addPacket("This is the Automated Message Exchange Format Object property!!","adasd");   

    //Add a child integer object
    object->addPacket(21213);

    //Add a child boolean object
    object->addPacket(true);

    AMEFObject *object2 = new AMEFObject();
    string j = "This is the property of a nested Automated Message Exchange Format Object";
    object2->addPacket(j);
    object2->addPacket(134123);
    object2->addPacket(false);

    //Add a child character object
    object2->addPacket('d');

    //Add a child AMEF Object
    object->addPacket(object2);

    //Encode the AMEF obejct
    string str = new AMEFEncoder()->encode(object,false);

用Java解码就像

    string arr = amef encoded byte array value;
    AMEFDecoder decoder = new AMEFDecoder()
    AMEFObject object1 = AMEFDecoder.decode(arr,true);

协议实现具有适用于C ++和Java的编解码器,有趣的是它可以保留名称/值对形式的对象类表示形式,当我偶然偶然发现该协议时,我在上一个项目中需要一个类似的协议根据我的要求修改了基础库。希望对您有帮助。




2

我建议研究通常用作序列化基础的Abstract工厂

我在另一个有关C ++工厂的问题中回答了。请查看那里是否有一家灵活的工厂。我尝试描述从ET ++使用宏的一种旧方法,该方法对我非常有用。

ET ++是一个将旧MacApp移植到C ++和X11的项目。在它的努力下,Eric Gamma等开始考虑“ 设计模式”。ET ++包含在运行时自动进行序列化和自省的方式。


0

如果您想要简单,最佳的性能并且不关心向后数据兼容性,请尝试HPS,它重量轻,比Boost等快得多,并且比Protobuf等更容易使用。

例:

std::vector<int> data({22, 333, -4444});
std::string serialized = hps::serialize_to_string(data);
auto parsed = hps::parse_from_string<std::vector<int>>(serialized);

0

这是我敲的一个简单的序列化程序库。它仅是c11的标头,并包含用于序列化基本类型的示例。这是一张上课的地图。

https://github.com/goblinhack/simple-c-plus-plus-serializer

#include "c_plus_plus_serializer.h"

class Custom {
public:
    int a;
    std::string b;
    std::vector c;

    friend std::ostream& operator<<(std::ostream &out, 
                                    Bits my)
    {
        out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
        return (out);
    }

    friend std::istream& operator>>(std::istream &in, 
                                    Bits my)
    {
        in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
        return (in);
    }

    friend std::ostream& operator<<(std::ostream &out, 
                                    class Custom &my)
    {
        out << "a:" << my.a << " b:" << my.b;

        out << " c:[" << my.c.size() << " elems]:";
        for (auto v : my.c) {
            out << v << " ";
        }
        out << std::endl;

        return (out);
    }
};

static void save_map_key_string_value_custom (const std::string filename)
{
    std::cout << "save to " << filename << std::endl;
    std::ofstream out(filename, std::ios::binary );

    std::map< std::string, class Custom > m;

    auto c1 = Custom();
    c1.a = 1;
    c1.b = "hello";
    std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
    std::vector l1(L1);
    c1.c = l1;

    auto c2 = Custom();
    c2.a = 2;
    c2.b = "there";
    std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
    std::vector l2(L2);
    c2.c = l2;

    m.insert(std::make_pair(std::string("key1"), c1));
    m.insert(std::make_pair(std::string("key2"), c2));

    out << bits(m);
}

static void load_map_key_string_value_custom (const std::string filename)
{
    std::cout << "read from " << filename << std::endl;
    std::ifstream in(filename);

    std::map< std::string, class Custom > m;

    in >> bits(m);
    std::cout << std::endl;

    std::cout << "m = " << m.size() << " list-elems { " << std::endl;
    for (auto i : m) {
        std::cout << "    [" << i.first << "] = " << i.second;
    }
    std::cout << "}" << std::endl;
}

void map_custom_class_example (void)
{
    std::cout << "map key string, value class" << std::endl;
    std::cout << "============================" << std::endl;
    save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    std::cout << std::endl;
}

输出:

map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin

m = 2 list-elems {
    [key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
    [key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}

0

我正在使用以下模板来实现序列化:

template <class T, class Mode = void> struct Serializer
{
    template <class OutputCharIterator>
    static void serializeImpl(const T &object, OutputCharIterator &&it)
    {
        object.template serializeThis<Mode>(it);
    }

    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        return T::template deserializeFrom<Mode>(it, end);
    }
};

template <class Mode = void, class T, class OutputCharIterator>
void serialize(const T &object, OutputCharIterator &&it)
{
    Serializer<T, Mode>::serializeImpl(object, it);
}

template <class T, class Mode = void, class InputCharIterator>
T deserialize(InputCharIterator &&it, InputCharIterator &&end)
{
    return Serializer<T, Mode>::deserializeImpl(it, end);
}

template <class Mode = void, class T, class InputCharIterator>
void deserialize(T &result, InputCharIterator &&it, InputCharIterator &&end)
{
    result = Serializer<T, Mode>::deserializeImpl(it, end);
}

T是您要序列化Mode的类型,是一种虚拟类型,用于区分不同类型的序列化,例如。相同的整数可以序列化为little endian,big endian,varint等。

默认情况下,Serializer委托将任务委托给要序列化的对象。对于内置类型,您应该对进行模板专用化Serializer

还提供了便利功能模板。

例如无符号整数的Little Endian序列化:

struct LittleEndianMode
{
};

template <class T>
struct Serializer<
    T, std::enable_if_t<std::is_unsigned<T>::value, LittleEndianMode>>
{
    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        T res = 0;

        for (size_t i = 0; i < sizeof(T); i++)
        {
            if (it == end) break;
            res |= static_cast<T>(*it) << (CHAR_BIT * i);
            it++;
        }

        return res;
    }

    template <class OutputCharIterator>
    static void serializeImpl(T number, OutputCharIterator &&it)
    {
        for (size_t i = 0; i < sizeof(T); i++)
        {
            *it = (number >> (CHAR_BIT * i)) & 0xFF;
            it++;
        }
    }
};

然后进行序列化:

std::vector<char> serialized;
uint32_t val = 42;
serialize<LittleEndianMode>(val, std::back_inserter(serialized));

反序列化:

uint32_t val;
deserialize(val, serialized.begin(), serialized.end());

由于抽象的迭代器逻辑,它应与任何迭代器(例如流迭代器),指针等一起使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.