【问题标题】:Serializing a class which contains a std::string序列化包含 std::string 的类
【发布时间】:2011-10-26 03:37:32
【问题描述】:

我不是 C++ 专家,但我过去曾多次对事物进行序列化。不幸的是,这一次我试图序列化一个包含 std::string 的类,我理解这很像序列化指针。

我可以将类写出到文件中,然后再次读回。所有 int 字段都很好,但 std::string 字段给出了“地址越界”错误,可能是因为它指向的数据不再存在。

对此有标准的解决方法吗?我不想回到 char 数组,但至少我知道它们在这种情况下工作。如有必要,我可以提供代码,但我希望我已经很好地解释了我的问题。

我通过将类转换为 char* 并使用 fstream 将其写入文件来进行序列化。阅读当然正好相反。

【问题讨论】:

  • IMO,您必须手动转储字符串数据。准备一个具有字符缓冲区和字符串长度的普通结构并将其序列化而不是原始对象。
  • 唯一真正的问题是如何分隔字符串,但使用 char 数组也会遇到这个问题。我没有得到你遇到麻烦的地方,似乎很容易序列化一个字符串给我。也许你最好发布一些代码。
  • java 有标准的序列化(在标准库中)。 C++ 在语言和 STL 库中都没有这样的功能。有一个外部库可以做到这一点,例如boost可以做到这一点。其他变体是使用 google 的协议缓冲区。
  • 吹毛求疵:你正在序列化一个对象
  • 中间结构对我来说确实有意义。这确实引出了一个问题,为什么我首先要为这些字符串烦恼,从长远来看,这似乎是一种虚假的经济。

标签: c++ serialization binaryfiles


【解决方案1】:

我通过将类转换为 char* 并将其写入 文件与 fstream。阅读当然正好相反。

不幸的是,这仅在不涉及指针的情况下才有效。你可能想给你的课程void MyClass::serialize(std::ostream)void MyClass::deserialize(std::ifstream),然后打电话给他们。对于这种情况,您需要

std::ostream& MyClass::serialize(std::ostream &out) const {
    out << height;
    out << ',' //number seperator
    out << width;
    out << ',' //number seperator
    out << name.size(); //serialize size of string
    out << ',' //number seperator
    out << name; //serialize characters of string
    return out;
}
std::istream& MyClass::deserialize(std::istream &in) {
    if (in) {
        int len=0;
        char comma;
        in >> height;
        in >> comma; //read in the seperator
        in >> width;
        in >> comma; //read in the seperator
        in >> len;  //deserialize size of string
        in >> comma; //read in the seperator
        if (in && len) {
            std::vector<char> tmp(len);
            in.read(tmp.data() , len); //deserialize characters of string
            name.assign(tmp.data(), len);
        }
    }
    return in;
}

您可能还希望重载流运算符以便于使用。

std::ostream &operator<<(std::ostream& out, const MyClass &obj)
{obj.serialize(out); return out;}
std::istream &operator>>(std::istream& in, MyClass &obj)
{obj.deserialize(in); return in;}

【讨论】:

  • 看起来很有趣,并且不会对现有代码/工作流程造成太大的破坏。我会有戏。谢谢
  • (1) 您的流需要通过引用传递,istream 和 ostream 复制构造函数被禁用。 (2) 宽度和高度以及字符串的大小将在输出时连接在一起,因此将它们读回将产生一个数字。
  • in.read(&amp;name[0], len); 这肯定是错误的。您不能将字符串视为向量。即使作为向量,如果 len == 0 也会失败。
  • @john:同意。似乎需要一个中间的char *nameValue = new char[len + 1];
  • @Benjamin Lindley:哎呀,我忘了通过引用来制作它们。我的错。
【解决方案2】:

简单地将对象的二进制内容写入文件不仅不可移植,而且正如您所认识到的,它不适用于指针数据。你基本上有两个选择:要么你写一个真正的序列化库,它通过例如正确处理 std::strings使用c_str() 将实际字符串输出到文件中,或者使用优秀的boost serialization 库。如果可能的话,我会推荐后者,然后您可以使用这样的简单代码进行序列化:

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/string.hpp>

class A {
    private:
        std::string s;
    public:
        template<class Archive>
        void serialize(Archive& ar, const unsigned int version)
        {
            ar & s;
        }
};

这里,函数serialize 用于对数据进行序列化和反序列化,具体取决于您如何调用它。有关详细信息,请参阅文档。

【讨论】:

  • 好主意。但是,看起来好像您展示了“后者”的示例-使用 boost,而您建议“前者”...
  • 我之前没有研究过提升,但接下来我会检查一下。谢谢
【解决方案3】:

对于具有可变大小的字符串或其他 blob,最简单的序列化方法是在序列化整数时首先序列化大小,然后将内容复制到输出流。

读取时首先读取大小,然后分配字符串,然后通过从流中读取正确数量的字节来填充它。

另一种方法是使用分隔符和转义,但需要更多代码,并且在序列化和反序列化时速度较慢(但结果可以保持人类可读)。

【讨论】:

    【解决方案4】:

    如果您的类包含任何外部数据(string 确实如此),您将不得不使用比将类转换为 char* 并将其写入文件更复杂的序列化方法。你是正确的,为什么你会遇到分段错误。

    我会创建一个成员函数,它接受 fstream 并从中读取数据,以及一个反函数,它将接受 fstream 并将其内容写入它以便稍后恢复,如下所示:

    class MyClass {
    pubic:
        MyClass() : str() { }
    
        void serialize(ostream& out) {
            out << str;
        }
    
        void restore(istream& in) {
            in >> str;
        }
    
        string& data() const { return str; }
    
    private:
        string str;
    };
    
    MyClass c;
    c.serialize(output);
    
    // later
    c.restore(input);
    

    如果您想要语法糖,您还可以定义 operator&lt;&lt;operator&gt;&gt; 以与 istreamostream 一起使用来序列化和恢复您的类。

    【讨论】:

    • 如果用作成员函数,写/读操作会有所不同吗?我真的不明白如何写实际字符而不是指针地址。
    • @iwasinnamuknow:没有写入和读取操作在用作成员函数时不会有不同的行为,是什么让您有这样的想法?
    • @iwasinnamuknow 它在string 上使用operator&lt;&lt;&gt;&gt; string,该string 被定义为将字符串的内容写入文件。显然,您的数据成员比一个字符串多,因此您只需将它们全部写入输出文件,然后以相同的顺序从输入文件中读取它们。
    • @john 这只是一个简单的例子。
    【解决方案5】:

    为什么不只是类似的东西:

    std::ofstream ofs;
    ...
    
    ofs << my_str;
    

    然后:

    std::ifstream ifs;
    ...
    
    ifs >> my_str; 
    

    【讨论】:

    • 不会假设字符串与其他任何东西分开吗?我正在尝试一次性编写/阅读整个课程及其内容。
    • 如果其中夹杂着其他数据,或者字符串中有空格,则输入不准确。
    • 这是否适用于包含空格和/或换行符的字符串?
    • @iwas:你不能简单地将一个类重新解释为char *。通常,对象的序列化需要(半)手动依次序列化每个成员变量。我不太确定你在寻找什么样的解决方案!
    • @Oli:这肯定是重点,OP 声称序列化 std::string 比序列化 char 数组更难。这是我不明白的一点,在他解释自己之前,我认为我们不会走得太远。
    【解决方案6】:
    /*!
     * reads binary data into the string.
     * @status : OK.
    */
    
    class UReadBinaryString
    {
        static std::string read(std::istream &is, uint32_t size)
        {
            std::string returnStr;
            if(size > 0)
            {
                CWrapPtr<char> buff(new char[size]);       // custom smart pointer
                is.read(reinterpret_cast<char*>(buff.m_obj), size);
                returnStr.assign(buff.m_obj, size);
            }
    
            return returnStr;
        }
    };
    
    class objHeader
    {
    public:
        std::string m_ID;
    
        // serialize
        std::ostream &operator << (std::ostream &os)
        {
            uint32_t size = (m_ID.length());
            os.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
            os.write(m_ID.c_str(), size);
    
            return os;
        }
        // de-serialize
        std::istream &operator >> (std::istream &is)
        {
            uint32_t size;
            is.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
            m_ID = UReadBinaryString::read(is, size);
    
            return is;
         }
    };
    

    【讨论】:

    • @RocketR。我写了工会吗?修好了。你知道它是我的一些旧项目文件中代码部分的快速过去..
    【解决方案7】:

    我已经很久没有编写 C++ 代码了,但也许你可以序列化一个 char 的数组。

    然后,当您打开文件时,您的 string 将只指向数组。

    只是一个想法。

    【讨论】:

    • LPTSTR 不可移植(仅限 Windows)。
    • 不过他不想重新使用数组。
    • 我并不反对 char 数组,但我一直在努力使用 std::strings 代替,厌倦了被告知我是老式的。如果他们让事情变得更容易,那么我可能不得不回去。
    • @osgx:我不是说存储LPTSTR。我是说序列化char[MAX] 并将其读入您的字符串。
    • 他们肯定不会让事情变得更容易。但是为什么不读入一个 char 数组,然后将你的 char 数组分配给一个字符串呢?这很难吗?
    猜你喜欢
    • 1970-01-01
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-20
    • 1970-01-01
    相关资源
    最近更新 更多