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

Posted

技术标签:

【中文标题】如何在 C++ 中序列化对象?【英文标题】:How do you serialize an object in C++? 【发布时间】:2010-10-06 03:35:55 【问题描述】:

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

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

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

【问题讨论】:

查看google::protobuf,它是一个非常强大且快速的二进制序列化库。我们已经成功地将它与 boost::asio 等一起使用。 看看 [STLPLUS][1],具有持久性实现的库。 [1]:stlplus.sourceforge.net 所提供的答案实际上并没有解释 如何 进行序列化。一个提供了 boost 序列化库,另一个解释了简单实现中的陷阱。既然这是c++-faq 的问题,真的有人能回答吗? 【参考方案1】:

说到序列化,我想到了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
    

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

还有一些机制可以让您处理指针的序列化(复杂的数据结构,如树等没有问题),派生类,您可以在二进制和文本序列化之间进行选择。此外,所有 STL 容器都支持开箱即用。

【讨论】:

这是一个c++问题,gps_position类怎么会重载 注意“朋友类 boost::serialization::access”。这为类成员提供了对序列化库函数的访问,即使它们是私有的。【参考方案2】:

在某些情况下,在处理简单类型时,您可以这样做:

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

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

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

您遇到以下问题:

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

(另外,您需要知道在接收方要解包的内容。)

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

但是,通过boost's serialization library 处理这种繁重的工作要好得多,也更容易。

【讨论】:

这是我正在考虑的事情。但是因为我想序列化到网络流,所以这根本不起作用。最多是因为字节序和不同的平台。但我不知道它会破坏虚拟指针。谢谢=)【参考方案3】:

您可以使用通用模式来序列化对象。基本原语是您可以从迭代器中读取和写入的这两个函数:

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.

对于类,您可以使用友元函数模式来允许使用 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&lt;uint8_t&gt; &amp;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.

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

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

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

序列化函数指针

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

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

序列化多态类型

既然我说你应该避免在可序列化类型中使用指针,而应该使用数组索引,那么多态性就无法工作,因为它需要指针。

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

版本控制

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

在这种情况下,每个对象都应该在其序列化的开头写一个版本号来表示版本。

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

每次发生变化时,您都应该增加版本号。


所以总结一下,序列化可能很复杂。但幸运的是,您不需要序列化程序中的所有内容,通常只序列化协议消息,这些消息通常是普通的旧结构。所以你不需要我上面经常提到的复杂技巧。

【讨论】:

谢谢。此答案包含与在 C++ 中序列化结构化数据相关的概念的一个很好的概述。【参考方案4】:

通过学习,我编写了一个简单的 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);

【讨论】:

以上是关于如何在 C++ 中序列化对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 C++ 本机对象编组到托管 C++ CLI

在 C++ 中使用指针和指向指针的指针读取和存储序列化对象的快速方法

C++ Boost 序列化、构造函数和数据复制

如何实现C++类与XML文件的转换

将 C++ 类序列化为文件,然后在 Python 中进行基于事件的反序列​​化?

用C++优雅的实现对象到文件的序列化/反序列化