C++:如何在不使用库的情况下序列化/反序列化对象?

11

我正在尝试了解在C++中如何在不使用库的情况下进行序列化/反序列化。我从简单的对象开始,但是当反序列化向量时,我发现在没有先写入其大小的情况下无法获取该向量。此外,我不知道应该选择哪种文件格式,因为如果在向量大小之前存在数字,则我无法正确读取它。此外,我想要对类和映射容器执行此操作。我的任务是对像这样的对象进行序列化/反序列化:

PersonInfo
{
    unsigned int    age_;
    string name_;
    enum { undef, man, woman } sex_;
}

Person : PersonInfo 
{
    vector<Person>      children_;
    map<string, PersonInfo>     addrBook_;
}

目前我知道如何序列化像这样的简单对象:

vector<PersonInfo> vecPersonInfo;
vecPersonInfo.push_back(*personInfo);
vecPersonInfo.push_back(*oneMorePersonInfo);

ofstream file("file", ios::out | ios::binary);
if (!file) {
    cout<<"can not open file";
} else {
    vector<PersonInfo>::const_iterator iterator = vecPersonInfo.begin();
    for (; iterator != vecPersonInfo.end(); iterator++) {
        file<<*iterator;
    }

能否请您建议一下,如何对这个复杂对象进行操作,或者有没有讲解清晰的好教程可以参考?


为什么你不能使用一个库? - KillianDS
2
@KillianDS 或许他想学习。正如他所说,他正在尝试理解它是如何完成的。 - RedX
1
没错,这只是为了教育目的。 - Winte Winte
@redx 或许是想避免使用库,因为它们很难分发,或者这是作业之类的原因,所以我才会问。 - KillianDS
3
不使用库进行序列化的问题在所有语言中都是一样的:你需要“知道”你正在进行反序列化的内容,否则在序列化时要做好记录。然后选择最合适的格式满足你的需求,并编写相应的解析器/生成器... - David Rodríguez - dribeas
David的评论简洁地概括了它。 - Preet Kukreti
2个回答

14

一种模式是实现一个抽象类来定义序列化函数,该类定义了什么进入序列化器和什么从中出来。下面是一个例子:

class Serializable
{
public:
    Serializable(){}
    virtual ~Serializable(){}

    virtual void serialize(std::ostream& stream) = 0;
    virtual void deserialize(std::istream& stream) = 0;
};

你需要为想要序列化的类/结构体实现 Serializable 接口:

struct PersonInfo : public Serializable // Yes! It's possible
{
    unsigned int age_;
    string name_;
    enum { undef, man, woman } sex_;

    virtual void serialize(std::ostream& stream)
    {
        // Serialization code
        stream << age_ << name_ << sex_;
    }

    virtual void deserialize(std::istream& stream)
    {
        // Deserialization code
        stream >> age_ >> name_ >> sex_;
    }
};

剩下的部分我相信你已经知道了。以下是几个需要克服的障碍,你可以在空闲时间里解决:

  1. 当你向流中写入带有空格的字符串并尝试读取它时,你只会得到其中的一部分,其余的字符串会“破坏”之后读取的值。
  2. 如何编写跨平台(小端 vs. 大端)的代码。
  3. 当反序列化时,如何让你的程序自动检测要创建哪个类。

提示:

  1. 使用自定义的序列化器,具有写入布尔值、整数、浮点数、字符串等函数。
  2. 使用字符串来表示正在序列化的对象类型,并使用工厂在反序列化时创建该对象的实例。
  3. 使用预定义的宏来确定编译代码的平台。
  4. 总是以固定的字节序写入文件,并使使用其他字节序的平台适应它。

3
这样做会失败,因为您没有添加分隔符,性别(枚举值)将被读作名称的一部分。此外,如果名称有多个单词,则只读取第一个单词...问题比提供的简单解决方案更复杂。 - David Rodríguez - dribeas

2
最基本的形式是定义一个“Serializable”接口(抽象类),该接口定义虚拟读/写方法。您还需要定义一个“Stream”接口,为基本原始类型(例如,读取/写入整数、浮点数、字节、字符、搜索/重置等)提供通用API,以及可能为某些复合类型(值数组,例如字符串、向量等)提供操作流的API。如果适合您,可以使用C++ IOStreams。
您还需要为工厂创建一些ID系统,以便在加载/反序列化时创建相应的类,并用于引用序列化复杂类型时,使每个逻辑部分在必要时标记/标头具有正确的结构/长度信息。
然后,您可以为每个介质(如文本文件、二进制文件、内存、网络等)创建具体的Stream类。
您希望序列化的每个类都必须继承Serializable接口并实现详细信息(递归地利用为其他类型定义的可序列化接口,如果是复合/复杂类)。
当然,这是一种天真和“侵入式”的添加序列化的方式(您必须修改参与的类)。然后,您可以使用模板或预处理器技巧使其不那么侵入性。请参见Boost或协议缓冲区,或任何其他库,了解在代码中如何实现此功能的想法。
您真的确定要自己开发吗?这可能会变得非常混乱,特别是当您拥有指针、对象之间的指针(包括循环)时,您还需要在当前运行之前修复/转换它们。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接