2011-04-18 78 views
2

我想使用ifstream和ofstream序列化一个普通的旧数据结构,我无法让它工作。然后,我试图将我的问题简化为只是一个char和int的超基本序列化,甚至没有工作。很明显,我错过了核心基础层面的一些东西。无法使用ifstream和ofstream序列化二进制数据

对于基本结构:

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在最后只是一个问题吗?该安置新我结束了同值的SerializeTestStruct后:0,0。

+0

为什么不在流中使用插入/提取操作符? – 2011-04-18 19:31:22

+0

如果允许使用其他库,这已经在C++中解决了。你这样做只是为了让自己受益? – wheaties 2011-04-18 19:37:46

+2

@arasmussen:插入/提取操作符用于格式化文本I/O。他想要无格式的二进制I/O。 – 2011-04-18 19:41:09

回答

1

这是你的东西应该如何阅读:

#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++使用析构函数清理你,这就是他们的目的。这意味着如果所使用的变量的作用域相对有限,则使基本块包含代码的一部分。
  • 不要不必要地使用标志。

错误......

  • 不要使用::std::stringdata成员函数。
  • 使用放置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。这并不重要,因为该空间从来没有被使用过,只是存在于以下位置,以便访问下面的一个有效的4字节边界。

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

第一条规则,如果你需要一个serialize功能,你需要一个deserialize功能。第二条规则,除非你真的知道你在做什么,不要将原始内存转储到文件中。这在很多情况下都可以正常工作,但是有些情况下无法正常工作。除非你意识到什么是行不通的,什么时候行不通,什么时候行或行不行,你最终会得到在某些测试情况下似乎可以正常工作的代码,但是当你试图在真正的系统。

我的代码仍然将内存转储到文件中。只要您在完全相同的体系结构和平台上读取结果,并使用编译器的相同版本编译的代码编写代码,就可以运行它。只要其中一个变量发生变化,所有投注都将关闭。

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; 
} 
0

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

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

为什么不:

if (ofs) { 
    // stuff 
} 
+1

我同意!我为此道歉。我相信这是从我早些时候玩旗子的时候开始的,并且一定要清理掉。修复它并不能解决核心问题。 – MukMuk 2011-04-18 19:45:50

1

的问题是在这里:

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

这将构建SerializeTestStruct类型的值初始化对象在以前分配的内存。它将用零填充memblock,因为对于POD类型(more info),值初始化零初始化

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

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; 
+0

这肯定会是我看到零初始化结构的原因。然后,我需要做些什么来完全重建我的结构? – MukMuk 2011-04-18 20:26:37

+0

在从文件读取数据之前,您需要分配和初始化内存*。看看我的答案中的示例。 – 2011-04-18 20:54:09

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; 
} 

我停止二进制数据写入到支持文本数据流,因为文本数据不必担心字节序或接收平台接受哪种IEEE浮点格式。