是否可以在 C++ 中序列化和反序列化一个类?

Posted

技术标签:

【中文标题】是否可以在 C++ 中序列化和反序列化一个类?【英文标题】:Is it possible to serialize and deserialize a class in C++? 【发布时间】:2010-09-19 01:58:20 【问题描述】:

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

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

举个例子会很有帮助。

【问题讨论】:

不确定您所说的“本机”是什么意思,您是指本机 C++(如 Boost.Serialization)吗?你的意思是只使用 C++ 标准库吗?你还有别的意思吗? 我的意思是“不是外部软件库”。对不起,我的英语不是很好:S。我来自阿根廷 没有原生的方法来序列化一个对象(你仍然可以从 POD 中转储二进制数据,但你不会得到你想要的)。尽管如此,Boost 虽然不是“内部库”,但它是您应该考虑添加到编译器的第一个外部库。 Boost 具有 STL 质量(即 Top Gun C++) 【参考方案1】:

Boost::serialization 库相当优雅地处理了这个问题。我已经在几个项目中使用过它。有一个示例程序,展示了如何使用它,here。

唯一的本地方法是使用流。这基本上就是Boost::serialization 库所做的所有事情,它通过设置框架将对象写入类似文本的格式并从相同格式读取它们来扩展流方法。

对于内置类型,或者您自己的具有正确定义的operator<<operator>> 的类型,这相当简单;请参阅the C++ FAQ 了解更多信息。

【讨论】:

在我看来 boost::serialization 要求调用者跟踪对象的写入和读取顺序。那是对的吗?因此,如果在程序版本之间写入两个字段的顺序发生变化,那么我们就会出现不兼容性。是这样吗? 这可能是由于序列化函数,而不是 Boost::serialization 代码本身。 @0xDEADBEEF:这可能是在使用 binary_(i|o)archive 时发生的,它引入了其他“问题”,如字节序。试试 text_(i|o)archive,它与平台无关。 一个特定的框架/库解决方案不应该是公认的答案。 @Andrea:Boost 库是一个特例。在 C++11 最终确定之前,没有它几乎不可能编写现代 C++ 代码,因此它更接近于二级 STL,而不是一个单独的库。【参考方案2】:

我知道这是一篇旧帖子,但它是搜索c++ serialization 时最先出现的帖子之一。

我鼓励任何有权访问 C++11 的人查看 cereal,这是一个仅用于序列化的 C++11 标头库,支持开箱即用的二进制、JSON 和 XML。谷物被设计为易于扩展和使用,并具有与 Boost 相似的语法。

【讨论】:

谷物的好处在于,与 boost 不同,它具有最少的元数据(几乎没有)。 boost::serialization 变得非常烦人,因为每次打开档案时,它都会将其 lib 版本写入流,这使得附加到文件变得不可能。 @Cyber​​Snoopy - 创建存档时有一个标志用于禁止此功能 - 当然您在阅读存档时也必须记住它。【参考方案3】:

Boost 是一个很好的建议。但如果你想自己动手,也不是那么难。

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

我写了一篇文章,描述了一个基本(但仍然很强大)的序列化系统。你可能会觉得很有趣:Using SQLite as an On-disk File Format, Part 2。

【讨论】:

【参考方案4】:

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

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

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

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

【讨论】:

当然,这不可能是正确的......实现的<< 运算符用于打印对象的人类可读文本表示,这通常不是您想要的序列化。跨度> @einpoklum 不要为通用 ostream 定义 <<,而是尝试为文件流定义它。 @Carcigenicate:采用人类可读文本的日志文件是文件流。 @einpoklum 我不太清楚你的意思。不过弗兰克是对的,这些运算符可用于序列化。我只是将它们定义为序列化/反序列化向量。 我认为关键就在这里 “你应该重写 << 以将你的对象输出到某个序列化上下文……每个对象都负责输出它的……”——问题是关于如何避免为每个对象费力地写出来:语言或库有多大帮助?【参考方案5】:

我推荐谷歌protocol buffers。 我有机会在一个新项目上试用该库,它非常易于使用。 该库针对性能进行了大量优化。

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

【讨论】:

您是否有过使用它序列化大小约为 10-50MB 的对象的经验?文档似乎说协议缓冲区最适合大小约为 MB 的对象。 我推出了自己的库,(还)不使用流,所以它真的是为了小东西:gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767【参考方案6】:

Boost::serialization 是一个不错的选择,但我遇到了一个新项目:Cereal,我觉得它更优雅!我强烈建议调查一下。

【讨论】:

【参考方案7】:

您可以查看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);

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

【讨论】:

【参考方案8】:

我建议使用其他海报所描述的 boost 序列化。这是一个关于如何使用它的详细教程,很好地补充了 boost 教程:http://www.ocoudert.com/blog/2011/07/09/a-practical-guide-to-c-serialization/

【讨论】:

【参考方案9】:

Sweet Persist 是另一个。

可以在 XML、JSON、Lua 和二进制格式的流之间进行序列化。

【讨论】:

那个网站好像挂了,我只能找到这个旧的仓库:github.com/cwbaker/sweet_persist【参考方案10】:

我建议研究一下经常用作序列化基础的抽象工厂

我已经回答了关于 C++ 工厂的另一个 SO 问题。如果对灵活工厂感兴趣,请参阅there。我尝试从 ET++ 中描述一种使用宏的旧方法,这对我来说非常有用。

ET++ 是一个将旧 MacApp 移植到 C++ 和 X11 的项目。在此过程中,Eric Gamma 等人开始考虑设计模式。 ET++ 包含在运行时进行序列化和自省的自动方法。

【讨论】:

【参考方案11】:

如果您想要简单且最佳的性能并且不关心向后数据兼容性,请尝试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);

【讨论】:

这看起来很棒,但没有维护。比需要代码复制(IDL)和单独编译器的 Google FlatBuffers 方便得多。【参考方案12】:

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

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 进行模板特化。

还提供了方便的功能模板。

例如无符号整数的小端序列化:

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());

由于抽象的迭代器逻辑,它应该适用于任何迭代器(例如流迭代器)、指针等。

【讨论】:

虽然我确实喜欢自动 boost 库,但很高兴知道一些易于阅读的模板代码,以展示如何快速简单地构建它。【参考方案13】:

这是我敲出来的一个简单的序列化程序库。它只是标题,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

【讨论】:

【参考方案14】:

C++14(推荐 C++17)boost prf

没有宏 无需显式编写代码

demo

【讨论】:

第一个链接已损坏。您的意思是链接到以下内容吗? boost.org/doc/libs/1_75_0/doc/html/boost_pfr.html

以上是关于是否可以在 C++ 中序列化和反序列化一个类?的主要内容,如果未能解决你的问题,请参考以下文章

从api逻辑任务和反序列化对象并反序列化json

c++中的序列化与反序列化怎么实现的?

序列化和反序列化

数据结构算法 (树 的基本算法)

C++ 序列化性能

Newtonsoft.Json.JsonConvert 从同一个类中序列化和反序列化