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

时间:2009-02-07 14:51:43

标签: c++ serialization marshalling c++-faq

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

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

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

4 个答案:

答案 0 :(得分:51)

谈到序列化,boost serialization API出现在我的脑海中。至于通过网络传输序列化数据,我要么使用Berkeley套接字,要么使用asio library

修改
如果要将对象序列化为字节数组,可以按以下方式使用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容器都是开箱即用的。

答案 1 :(得分:13)

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

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

这可以作为概念验证或初稿,因此团队中的其他成员可以继续处理其他部分。

但迟早,通常会更早,这会让你受伤!

您遇到以下问题:

  • 虚拟指针表将被破坏。
  • 指针(数据/成员/函数)将被破坏。
  • 不同机器上填充/对齐的差异。
  • Big / Little-Endian字节排序问题。
  • float / double实现的变化。

(另外你需要知道你在接收方打包的内容。)

您可以通过为每个班级开发自己的编组/解组方法来改进这一点。 (理想情况下是虚拟的,因此可以在子类中进行扩展。)一些简单的宏可以让你以大/小端中立的顺序快速写出不同的基本类型。

但是这种笨拙的工作要好得多,而且更容易通过boost's serialization library处理。

答案 2 :(得分:1)

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

序列化时,您将字节推送到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指针。构造函数从流中读取它想要的字节并设置对象中的字段。 如果系统设计良好并按对象字段顺序序列化字段,则只需将流传递给初始化列表中的字段构造函数,然后按正确的顺序对它们进行反序列化。

序列化对象图

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

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

基本上我们在这里做的是用数组索引替换指针。 现在,在序列化时,您可以像往常一样序列化此数组索引。您不必担心对象在目标系统的内存中的位置。只要确保它们也有相同的对象池。

所以我们需要序列化对象池。但是哪些?当您序列化对象图时,您不是仅仅序列化对象,而是序列化整个系统。这意味着系统的序列化不应该从系统的各个部分开始。这些对象不应该担心系统的其余部分,他们只需要序列化数组索引即可。您应该有一个系统序列化程序例程来编排系统的序列化,并遍历相关的对象池并序列化所有这些对象。

在接收端,反序列化其中对象的所有数组,重新创建所需的对象图。

序列化函数指针

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

由于两个程序都将此表编译到了thehelhelves中,因此只使用索引就可以了。

序列化多态类型

因为我说你应该避免使用可序列化类型的指针而你应该使用数组索引,所以多态不能正常工作,因为它需要指针。

你需要使用类型标签和联合来解决这个问题。

版本

除此之外。您可能希望不同版本的软件可以互操作。

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

当加载另一侧的对象时,较新的对象可能能够处理较旧的表示,但较旧的表示无法处理较新的表示,因此它们应该抛出异常。

每次更改内容时,都应该使用版本号。

因此,为了将其包装起来,序列化可能很复杂。但幸运的是,您不需要序列化程序中的所有内容,通常只会将协议消息序列化,这通常是简单的结构。因此,您不需要经常使用我上面提到的复杂技巧。

答案 3 :(得分:0)

通过学习,我编写了一个简单的C ++ 11序列化程序。我尝试了各种 其他更重量级的产品,但是想要一些我实际上可以 了解何时使用最新的g ++出错或无法编译( 麦片对我来说发生了一个非常不错的图书馆,但很复杂,我无法 克服编译器在升级时抛出的错误。)无论如何,它仅是标题 并处理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);
}