2

我正在尝试使用 ifstream 和 ofstream 序列化一个普通的旧数据结构,但我无法让它工作。然后,我试图将我的问题简化为仅 char 和 int 的超基本序列化,即使这样也没有用。显然,我在核心基础层面遗漏了一些东西。

对于基本结构:

struct SerializeTestStruct
{
    char mCharVal;
    unsigned int mIntVal;

    void Serialize(std::ofstream& ofs);
};

带序列化功能:

void SerializeTestStruct::Serialize(std::ofstream& ofs)
{
    bool isError = (false == ofs.good());
    if (false == isError)
    {
        ofs.write((char*)&mCharVal, sizeof(mCharVal));
        ofs.write((char*)&mIntVal, sizeof(mIntVal));
    }
}

为什么以下短程序会失败?

//ultra basic serialization test.
    SerializeTestStruct* testStruct = new SerializeTestStruct();
    testStruct->mCharVal = 'y';
    testStruct->mIntVal = 9;

    //write
    std::string testFileName = "test.bin";
    std::ofstream fileOut(testFileName.data());
    fileOut.open(testFileName.data(), std::ofstream::binary|std::ofstream::out);
    fileOut.clear();
    testStruct->Serialize(fileOut);

    fileOut.flush();
    fileOut.close();

    delete testStruct;

    //read
    char * memblock;
    std::ifstream fileIn (testFileName.data(), std::ifstream::in|std::ifstream::binary);
    if (fileIn.is_open())
    {
        // get length of file:
        fileIn.seekg (0, std::ifstream::end);
        int length = fileIn.tellg();
        fileIn.seekg (0, std::ifstream::beg);

        // allocate memory:
        memblock = new char [length];
        fileIn.read(memblock, length);
        fileIn.close();

        // read data as a block:
        SerializeTestStruct* testStruct2 = new(memblock) SerializeTestStruct();

        delete[] testStruct2;
    }

当我运行代码时,我注意到memblock顶部有一个“y”,所以它可能正在工作,这只是placement new最后的问题?在那个位置 new 之后,我最终得到一个SerializeTestStruct值:0、0。

4

5 回答 5

2

以下是您的内容应该如何阅读:

#include <fstream>
#include <string>
#include <stdexcept>

struct SerializeTestStruct
{
    char mCharVal;
    unsigned int mIntVal;

    void Serialize(::std::ostream &os);
    static SerializeTestStruct Deserialize(::std::istream &is);
};

void SerializeTestStruct::Serialize(std::ostream &os)
{
    if (os.good())
    {
        os.write((char*)&mCharVal, sizeof(mCharVal));
        os.write((char*)&mIntVal, sizeof(mIntVal));
    }
}

SerializeTestStruct SerializeTestStruct::Deserialize(std::istream &is)
{
        SerializeTestStruct retval;

    if (is.good())
    {
        is.read((char*)&retval.mCharVal, sizeof(retval.mCharVal));
        is.read((char*)&retval.mIntVal, sizeof(retval.mIntVal));
    }
    if (is.fail()) {
        throw ::std::runtime_error("failed to read full struct");
    }
    return retval;
}

int main(int argc, const char *argv[])
{
//ultra basic serialization test.

    // setup
    const ::std::string testFileName = "test.bin";

    // write
    {
        SerializeTestStruct testStruct;
        testStruct.mCharVal = 'y';
        testStruct.mIntVal = 9;

        ::std::ofstream fileOut(testFileName.c_str());
        fileOut.open(testFileName.c_str(),
                     std::ofstream::binary|std::ofstream::out);
        fileOut.clear();
        testStruct.Serialize(fileOut);
    }

    // read
    {
        ::std::ifstream fileIn (testFileName.c_str(),
                                std::ifstream::in|std::ifstream::binary);
        if (fileIn.is_open())
        {
            SerializeTestStruct testStruct =            \
                SerializeTestStruct::Deserialize(fileIn);

            ::std::cout << "testStruct.mCharVal == '" << testStruct.mCharVal
                        << "' && testStruct.mIntVal == " << testStruct.mIntVal
                        << '\n';
        }
    }
    return 0;
}

风格问题:

  • new如果你能帮上忙,不要用它来创造东西。堆栈分配的对象通常是您想要的,并且比您从堆中分配的任意生命周期对象更容易管理。如果您确实使用new,请考虑使用某种智能指针类型来帮助您管理生命周期。
  • 序列化和反序列化代码应该匹配,以便可以一起检查和更改它们。这使得此类代码的维护变得更加容易。
  • 依靠 C++ 使用析构函数为您清理东西,这就是它们的用途。这意味着如果使用的变量的范围相对有限,则制作包含部分代码的基本块。
  • 不要不必要地使用标志。

错误...

  • 不要使用 的data成员函数::std::string
  • 使用放置new和内存块的东西真的是个坏主意,因为它非常复杂。而且,如果您确实使用了它,那么您就不会像以前那样使用数组删除。最后,由于稍后解释的原因,它无论如何都不会工作。
  • 不要ofstream在您的函数采用的类型中使用,Serialize因为它是您不需要的功能的派生类。您应该始终使用具有所需功能的层次结构中的最基类,除非您有非常具体的理由不这样做。Serialize与基类的功能配合得很好ostream,因此请改用该类型。
  • 你的结构的磁盘布局和内存布局不匹配,所以你的放置新技术注定要失败。通常,如果你有一个serialize函数,你需要一个匹配的deserialize函数。

这是您的内存布局问题的进一步解释。在基于 x86_64 的 Linux 机器上,内存中的结构如下所示:

+------------+-----------+
|Byte number | contents  |
+============+===========+
|          0 |     0x79  |
|            | (aka 'y') |
+------------+-----------+
|          1 |   padding |
+------------+-----------+
|          3 |   padding |
+------------+-----------+
|          4 |   padding |
+------------+-----------+
|          5 |         9 |
+------------+-----------+
|          6 |         0 |
+------------+-----------+
|          7 |         0 |
+------------+-----------+
|          8 |         0 |
+------------+-----------+

padding部分的内容是未定义的,但通常是0. 但这并不重要,因为该空间从未使用过,只是存在,因此对以下内容的访问int位于有效的 4 字节边界上。

磁盘上的结构大小为 5 个字节,并且完全缺少填充部分。所以这意味着当你将它读入内存时,它根本不会与内存结构正确对齐,访问它可能会导致某种可怕的问题。

第一条规则,如果你需要一个serialize函数,你就需要一个deserialize函数。第二条规则,除非你真的知道自己在做什么,否则不要将原始内存转储到文件中。这在许多情况下都可以正常工作,但在某些重要情况下它不起作用。除非你知道什么有效,什么无效,什么时候有效,什么时候有效,否则你最终会得到在某些测试情况下似乎可以正常工作的代码,但当你尝试在真实系统。

我的代码仍然会将内存转储到文件中。只要您在完全相同的架构和平台上读取结果,并且使用与您编写时相同的编译器版本编译代码,它就应该可以工作。一旦其中一个变量发生变化,所有赌注都将取消。

于 2011-04-18T20:33:23.670 回答
1
bool isError = (false == ofs.good());
if (false == isError)
{
    ofs.write((char*)&mCharVal, sizeof(mCharVal));
    ofs.write((char*)&mIntVal, sizeof(mIntVal));
}

改成

if ( ofs.good() )
{
    ofs.write((char*)&mCharVal, sizeof(mCharVal));
    ofs.write((char*)&mIntVal, sizeof(mIntVal));
}

我会做:

ostream & operator << ( ostream &os, const SerializeTestStruct &mystruct )
{
  if ( ofs.good() )
  {
    os.write((char*)&mystruct.mCharVal, sizeof(mCharVal));
    os.write((char*)&mystruct.mIntVal, sizeof(mIntVal));
  }
  return os;
}
于 2011-04-18T19:34:34.920 回答
1

问题在这里:

SerializeTestStruct* testStruct2 = new(memblock) SerializeTestStruct();

SerializeTestStruct这将在先前分配的内存中构造类型的值初始化对象。它将memblock用零填充,因为值初始化是POD 类型的零初始化更多信息)。

这是您的代码的快速修复:

SerializeTestStruct* testStruct2 = new SerializeTestStruct;
fileIn.read( (char*)&testStruct2->mCharVal, sizeof(testStruct2->mCharVal) );
fileIn.read( (char*)&testStruct2->mIntVal, sizeof(testStruct2->mIntVal) );
fileIn.close();
// do some with testStruct2
// ...
delete testStruct2;
于 2011-04-18T19:47:20.293 回答
0

在我看来,您需要允许序列化到缓冲区而不是直接序列化到流。写入缓冲区允许嵌套或继承的类写入内存,然后可以将整个缓冲区写入流。将点点滴滴写入流中效率不高。

这是我在停止将二进制数据写入流之前炮制的东西:

struct Serialization_Interface
{
    //!  Returns size occupied on a stream.
    /*! Note:  size on the platform may be different.
     *  This method is used to allocate memory.
     */
    virtual size_t  size_on_stream(void) const = 0;

    //!  Stores the fields of the object to the given pointer.
    /*!  Pointer is incremented by the size on the stream.
     */
    virtual void    store_to_buffer(unsigned char *& p_buffer) const = 0;

    //!  Loads the object's fields from the buffer, advancing the pointer.
    virtual void    load_from_buffer(const unsigned char *& p_buffer) = 0;
};

struct Serialize_Test_Structure
  : Serialization_Interface
{
    char mCharVal;
    int  mIntVal;

    size_t  size_on_stream(void) const
    {
        return sizeof(mCharVal) + sizeof(mIntVal);
    }

    void  store_to_buffer(unsigned char *& p_buffer) const
    {
        *p_buffer++ = mCharVal;
        ((int&)(*p_buffer)) = mIntVal;
        p_buffer += sizeof(mIntVal);
        return;
    }

    void  load_from_buffer(const unsigned char *& p_buffer)
    {
         mCharVal = *p_buffer++;
         mIntVal = (const int&)(*p_buffer);
         p_buffer += sizeof(mIntVal);
         return;
    }
};


int main(void)
{
   struct Serialize_Test_Struct myStruct;
   myStruct.mCharVal = 'G';
   myStruct.mIntVal = 42;

   // Allocate a buffer:
   unsigned char * buffer = new unsigned char[](myStruct.size_on_stream());

   // Create output file.
   std::ofstream outfile("data.bin");

   // Does your design support this concept?
   unsigned char * p_buffer = buffer;
   myStruct.store_to_buffer(p_buffer);
   outfile.write((char *) buffer, myStruct.size_on_stream());

   outfile.close();
   return 0;
}

我停止将二进制数据写入流,转而使用文本数据,因为文本数据不必担心 Endianess 或接收平台接受哪种 IEEE 浮点格式。

于 2011-04-18T20:54:01.710 回答
0

我是唯一一个发现这完全不透明的人吗:

bool isError = (false == ofs.good());
if (false == isError) {
    // stuff
}

为什么不:

if ( ofs ) {
    // stuff
}
于 2011-04-18T19:43:18.180 回答