如何在C ++中序列化对象?


83

我有一个小的对象层次结构,需要通过套接字连接进行序列化和传输。我都需要序列化对象,然后根据它是什么类型对其进行反序列化。有没有一种简单的方法可以在C ++中做到这一点(就像Java中一样)?

是否有C ++序列化在线代码示例或教程?

编辑:为了清楚起见,我正在寻找将对象转换为字节数组,然后再转换为对象的方法。我可以处理套接字传输。


3
查看google :: protobuf,它是用于二进制序列化的非常强大且快速的库。我们已将其与boost :: asio等一起成功使用
。– Ketan

看看[STLPLUS] [1],具有持久性实现的lib。[1]:stlplus.sourceforge.net
lsalamon's

4
提供的答案实际上并没有说明如何序列化。一个提供了boost序列化库,另一个提供了天真的实现方式的陷阱。由于这是C ++常见问题,有人可以实际回答吗?
匿名

Answers:


55

在谈到序列化时,我想到了boost序列化API。至于通过网络传输序列化数据,我要么使用Berkeley套接字,要么使用asio库

编辑:
如果要将对象序列化为字节数组,则可以按以下方式使用boost序列化器(取自教程站点):

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;

public:
    gps_position(){};
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

实际的序列化非常简单:

#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);

    // create class instance
    const gps_position g(35, 59, 24.567f);

    // save data to archive
    {
        boost::archive::binary_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
        // archive and stream closed when destructors are called
    }

反序列化以类似的方式工作。

还有一些机制可以让您处理指针的序列化(像tress这样的复杂数据结构都没有问题),派生类,并且可以在二进制序列化和文本序列化之间进行选择。除开箱即用的所有STL容器。


这是一个c ++问题,gps_position类如何重载<<运算符。没有定义任何朋友功能
Vicente Bolea,2015年

注意“朋友类boost :: serialization :: access”。这提供了对类成员的序列化库函数的访问,即使它们是私有的也是如此。
罗伯特·拉米

13

在某些情况下,处理简单类型时,您可以执行以下操作:

object o;
socket.write(&o, sizeof(o));

作为概念验证或初稿没关系,因此团队中的其他成员可以继续从事其他部分的工作。

但是迟早,通常是早晚,这会让您受伤!

您遇到以下问题:

  • 虚拟指针表将被破坏。
  • 指向数据/成员/功能的指针将被破坏。
  • 不同机器上的填充/对齐方式的差异。
  • 大/小尾数字节排序问题。
  • 浮动/双精度实施的变化形式。

(此外,您需要知道接收方要拆包的内容。)

您可以通过为每个类开发自己的编组/解组方法来对此进行改进。(理想情况下是虚拟的,因此可以在子类中对其进行扩展。)一些简单的宏将使您能够以大/小尾数中性的顺序快速写出不同的基本类型。

但是,通过boost的序列化库可以更好,更轻松地完成这种繁琐的工作。


那是我在考虑的事情。但是,由于我要序列化为网络流,所以这根本不起作用。最多是因为字节顺序和平台不同。但是我不知道它会破坏虚拟指针。谢谢=)
Atmocreations 2011年

4

您可以使用一种通用模式来序列化对象。基本要素是可以从迭代器读取和写入的以下两个函数:

template <class OutputCharIterator>
void putByte(char byte, OutputCharIterator &&it)
{
    *it = byte;
    ++it;
}


template <class InputCharIterator>
char getByte(InputCharIterator &&it, InputCharIterator &&end)
{
    if (it == end)
    {
        throw std::runtime_error{"Unexpected end of stream."};
    }

    char byte = *it;
    ++it;
    return byte;
}

然后,序列化和反序列化功能遵循以下模式:

template <class OutputCharIterator>
void serialize(const YourType &obj, OutputCharIterator &&it)
{
    // Call putbyte or other serialize overloads.
}

template <class InputCharIterator>
void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end)
{
    // Call getByte or other deserialize overloads.
}

对于类,您可以使用friend函数模式来允许使用ADL查找重载:

class Foo
{
    int internal1, internal2;

    // So it can be found using ADL and it accesses private parts.
    template <class OutputCharIterator>
    friend void serialize(const Foo &obj, OutputCharIterator &&it)
    {
        // Call putByte or other serialize overloads.
    }

    // Deserialize similar.
};

您可以在程序中将和序列化为这样的文件:

std::ofstream file("savestate.bin");
serialize(yourObject, std::ostreambuf_iterator<char>(file));

然后阅读:

std::ifstream file("savestate.bin");
deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());

我在这里的旧答案:

序列化意味着将您的对象转换为二进制数据。反序列化意味着根据数据重新创建对象。

序列化时,您将字节推入uint8_t向量。反序列化时,您正在从uint8_t向量读取字节。

在序列化东西时肯定可以使用一些模式。

每个可序列化的类都应具有一个serialize(std::vector<uint8_t> &binaryData)或类似的签名函数,该函数会将其二进制表示形式写入提供的向量中。然后,此函数可以将此向量传递给其成员的序列化函数,以便他们也可以将其内容写入其中。

由于数据表示在不同的体系结构上可能不同。您需要找到一种方案来表示数据。

让我们从基础开始:

序列化整数数据

只需以小尾数顺序写入字节。或者,如果大小很重要,请使用varint表示形式。

以小尾数顺序进行序列化:

data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);

小端顺序反序列化:

integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

序列化浮点数据

据我所知,IEEE 754在这里拥有垄断地位。我不知道会使用其他东西进行浮动的主流架构。唯一可以不同的是字节顺序。一些体系结构使用小尾数法,其他体系结构使用大尾数字节顺序。这意味着您需要注意按哪个顺序将接收端的字节调大。另一个区别可以是对反常值,无穷大和NAN值的处理。但是只要您避免使用这些值,就可以了。

序列化:

uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...

反序列化正在倒退。注意您的体系结构的字节顺序!

序列化字符串

首先,您需要就编码达成共识。UTF-8很常见。然后以长度前缀的方式存储它:首先,使用上述方法存储字符串的长度,然后逐个字节地写入字符串。

序列化数组。

它们与字符串相同。您首先序列化一个表示数组大小的整数,然后序列化其中的每个对象。

序列化整个对象

就像我之前说的,他们应该有一种serialize向向量添加内容的方法。要反序列化一个对象,它应该有一个采用字节流的构造函数。它可以是一个,istream但在最简单的情况下,它可以只是一个引用uint8_t指针。构造函数从流中读取所需的字节,并在对象中设置字段。如果系统设计合理,并且可以按对象字段顺序对字段进行序列化,则只需将流传递给初始化程序列表中字段的构造函数,然后按正确的顺序对它们进行反序列化。

序列化对象图

首先,您需要确保这些对象是否确实要序列化。如果目标上存在这些对象的实例,则无需序列化它们。

现在,您发现需要序列化由指针指向的对象。指针问题仅在使用它们的程序中有效。您无法序列化指针,应停止在对象中使用它们。而是创建对象池。该对象池基本上是一个动态数组,其中包含“框”。这些框具有参考计数。非零引用计数表示活动对象,零表示空闲插槽。然后,创建类似于shared_ptr的智能指针,该指针不存储指向对象的指针,而是存储数组中的索引。您还需要在表示空指针的索引上达成共识,例如。-1。

基本上,我们在这里所做的是将指针替换为数组索引。现在,在序列化时,您可以照常序列化此数组索引。您无需担心对象将在目标系统上的内存中的位置。只要确保它们也具有相同的对象池即可。

因此,我们需要序列化对象池。但是哪一个呢?好吧,当序列化一个对象图时,您不仅要序列化一个对象,还要序列化整个系统。这意味着系统的序列化不应该从系统的一部分开始。这些对象不必担心系统的其余部分,它们只需要序列化数组索引即可。您应该有一个系统序列化程序例程,该例程可以协调系统的序列化过程,并遍历相关的对象池并对所有对象池进行序列化。

在接收端,对象内部的所有数组都将反序列化,从而重新创建所需的对象图。

序列化函数指针

不要将指针存储在对象中。有一个静态数组,其中包含指向这些函数的指针,并将索引存储在对象中。

由于这两个程序都将此表编译到它们的架子中,因此仅使用索引应该可以工作。

序列化多态类型

因为我说过应该避免使用可序列化类型的指针,而应使用数组索引,所以多态性是行不通的,因为它需要指针。

您需要使用类型标记和联合来解决此问题。

版本控制

最重要的是。您可能希望不同版本的软件可以互操作。

在这种情况下,每个对象应在其序列化开始时写一个版本号以指示版本。

在另一侧加载对象时,较新的对象也许能够处理较旧的表示,但是较旧的对象无法处理较新的表示,因此应对此抛出异常。

每次更改时,都应更改版本号。


因此,总结起来,序列化可能很复杂。但是幸运的是,您不需要序列化程序中的所有内容,大多数情况下仅协议消息被序列化,而这些消息通常是普通的旧结构。因此,您不需要我经常提到的复杂技巧。


1
谢谢。该答案很好地概述了与在C ++中序列化结构化数据有关的概念。
肖恩

0

通过学习,我编写了一个简单的C ++ 11序列化程序。我尝试了其他各种更重量级的产品,但想要当我出现问题或无法使用最新的g ++编译时(我在Cereal上发生过的事情;这是一个非常不错的库,但是很复杂,我无法理解),我可以真正理解它。无论如何,它仅是标头,可以处理POD类型,容器,地图等。没有版本控制,它只会从保存该文件的同一个文件中加载文件。

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

用法示例:

#include "c_plus_plus_serializer.h"

static void serialize (std::ofstream out)
{
    char a = 42;
    unsigned short b = 65535;
    int c = 123456;
    float d = std::numeric_limits<float>::max();
    double e = std::numeric_limits<double>::max();
    std::string f("hello");

    out << bits(a) << bits(b) << bits(c) << bits(d);
    out << bits(e) << bits(f);
}

static void deserialize (std::ifstream in)
{
    char a;
    unsigned short b;
    int c;
    float d;
    double e;
    std::string f;

    in >> bits(a) >> bits(b) >> bits(c) >> bits(d);
    in >> bits(e) >> bits(f);
}

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.