在C++中保存复杂对象的方法

Methods of saving complex objects in C++

本文关键字:对象 方法 复杂 保存 C++      更新时间:2023-10-16

我总是通过将ASCII写入文件来保存数据,即

param1 = value1
param2 = string string string

并且加载了大量烦人的解析开销。我只是试图通过将整个对象写入一个二进制文件,一个la 来提升我的编程游戏

class Record {
    int par1;
    string par2;
    vector<string> par3;
    void saveRecord(string fName);
    void loadRecord(string fName);
}
Record::saveRecord() {
    ...
    fstream outFile(fName.c_str(), fstream::out | fstream::binary);
    outFile.write( (char*)this, sizeof(Record) );
    outFile.close();
}

但我发现这不起作用,因为复杂的数据类型(例如字符串、向量)涉及的指针的值不能以这种方式存储。

所以听起来选项是

A)编写复杂的序列化算法,将每个复杂的数据类型转换为基元,然后保存为二进制;或

B)只需按照我的初始策略将所有内容写入ASCII文件

第一种方法似乎过于复杂,而第二种方法则显得优雅。

还有其他选择吗?有标准程序吗

注意:我看到了boost::序列化库,它看起来也很不优雅,而且异常繁琐——也就是说,如果这是正确的方法,我只会编写自己的序列化方法。

否。使用Boost.Serialization或Google协议缓冲区。是的,你必须编写函数,将你的数据放入串行化容器并从中提取。这就是为预期实际工作的健壮解决方案所做的工作。

通过这种方式,您可以获得二进制文件的版本控制、兼容性和可移植性。如果您将数据视为一堆字节,并写入/读取所有内容,那么当您更改结构时,或者当文件是从具有不同填充/字节大小/字节顺序的构建中写入时,您将无法读取旧文件。

它可能适用于简单的事情,但会很快崩溃,你会后悔没有从一开始就做好。

您提到的的两种策略

A) 编写复杂的序列化算法,将每个复杂的数据类型转换为基元,然后保存为二进制;或

B) 只需按照我最初的策略将所有内容写入ASCII文件即可。

是通常的做法。您实际上是在创建自己的文件格式。最常见的范式是大块范式。保存一个对象或一组对象时,首先要写一个int,表示对象或数据"块"的大小。下一个int表示对象的种类。如果您关心在用户升级软件时保存的支持配置,则可能还需要包含版本信息。

当您关心数据是否非常精确时,选项A非常有用,它使在c++中加载/保存有问题变得更容易。例如,以这种方式保存的浮点值将加载与保存的值完全相同的值。

当你想查看你正在保存的内容时,选项B很有用,也许对于一个人来说,可以用某种方式手动修改数据。保存在此处的浮子在装载回时将不完全相同。

请尝试查看其他文件格式的示例。Midi文件格式使用chunks范式,并具有流式传输功能,使用选项a。Wavefront"obj"文件格式用于3D应用程序,因为它使用选项B非常简单。所有内容都可以在您喜欢的文本编辑器中读取。

如果你想坚持基于文本的序列化,你可以尝试覆盖:

  • 用于序列化的std::ostream& operator <<(std::ostream& os, const Type& obj);,以及
  • 用于反序列化的std::istream& operator >>(std::istream& is, Type& obj);

该库已经序列化和反序列化了基元类型,您不需要访问类或模板的内部来编写自己的重写,C++程序员已经熟悉了这个概念。

例如,std::vector的序列化器/反序列化器可能看起来像:

template<class T, class Alloc>
std::ostream& operator <<(std::ostream& os, const std::vector<T, Alloc>& vec)
{   os << vec.size << 'n';
    for(std::vector<T, Alloc>::const_iterator i = vec.begin();
        i != vec.end(); ++i)
        os << *vec << 'n';
    return os;
}
template<class T, class Alloc>
std::istream& operator >>(std::istream& is, std::vector<T, Alloc>& vec)
{   vec.clear();
    size_t size = 0;
    is >> size;
    vec.reserve(size);
    while(size--)
    {   T temp;
        is >> temp;
        vec.push_back(temp);
    }
    return is;
}

请注意,这种方法有几个限制(留给读者练习)。你的工作是评估这些,并决定这是否是正确的方法。

我不知道有任何标准过程,这取决于您存储的数据。你所描述的是浅层和深层表达之间的区别(很像浅层或深层复制)。我建议根据存储的内容为每个类规划一个简化的序列化机制。正如您所指出的,当您的底层元素与类实例不连续(甚至是不连续的一部分)时,仅仅写出内存的字节表示是不够的。