【问题标题】:std::vector writing/reading back gives garbaggestd::vector 写/读回产生垃圾
【发布时间】:2015-02-24 00:13:06
【问题描述】:

我正在尝试将“产品”类的向量写入文件并将其读回。但是,我在阅读时会加载垃圾。有人可以回顾一下可能出了什么问题吗?或者建议另一种方法。

#include <fstream>
#include <vector>
#include <iostream>

class Product
{
  public:
      std::string name;
      int code;
      double price;
};


int
main ()
{
  const char *const file_name = "products.bin";
  {
    std::vector < Product > prod
    {
      Product {"Honey", 1, 12.34}, 
      Product {"CoffeeBeans", 2, 56.78}, 
      Product {"Cl", 3, 90.12}
    };

    for (const Product & p:prod)
       std::cout << p.name << ' ' << p.code << ' ' << p.price << '\n';

    std::ofstream file (file_name, std::ios::binary);
    size_t sz = prod.size ();

    file.write (reinterpret_cast < const char *>(&sz), sizeof (sz));
    file.write (reinterpret_cast < const char *>(&prod[0]), sz * sizeof (prod[0]));
  }

  {
    std::vector < Product > prod;
    std::ifstream file (file_name, std::ios::binary);

    size_t sz;
    file.read (reinterpret_cast < char *>(&sz), sizeof (sz));
    prod.resize (sz);
    file.read (reinterpret_cast < char *>(&prod[0]), sz * sizeof (prod[0]));

    for (const Product & p:prod)
         std::cout << p.name << ' ' << p.code << ' ' << p.price << '\n';
  }

}

> Blockquote

【问题讨论】:

  • 停止将非POD 类型转储到文件中。 Product 包含一个 std::string 成员。这不会轻而易举地进入,尤其是退出该文件。
  • 你怎么能写上面的代码,却没有意识到std::string不能被bin-dumped? ...您是否无意中从其他人那里复制了一些代码?
  • reinterpret_cast 表示您的代码是错误的,除非您明确知道为什么它实际上是正确的。
  • 更明确地说,std::string 会将文本存储在动态分配的内存中(除非它实现了可选的“短字符串优化”,那么可以在内部存储几个字符的字符串)。因此,您正在写出指向在存储 Products 的原始向量离开第一个范围并被销毁后仍然无效的文件的指针 - 所有那些 std::strings 都是 deleted 并且它们是动态的已释放分配的内存。
  • 也许考虑不为此使用二进制文件并使用file &lt;&lt; p.name &lt;&lt; '\n' &lt;&lt; p.code &lt;&lt; '\n' &lt;&lt; p.price &lt;&lt; '\n'之类的文本格式读取/写入您的值?

标签: c++ c++11 file-io stl stdvector


【解决方案1】:

标准库成员std::ostream::write 将原始数据写入输出流。作为该写入的条件,所述数据本质上必须是trivially copyable

您的Product 不是这样的类型。它包含一个std::string 成员。 std::string(通常)是使用由对象内部指针管理的动态缓冲区实现的。因此它不是一个普通可复制类型,因此Product 不是一个普通可复制类型。

如果您不希望以原始字节存储这些数据,则需要一个协议,读者和作者都同意。该协议变得多么复杂完全取决于您以及您的最终需求有多么多样化,并且由于您是双方的作者,因此这些决定完全取决于您。如果目标文件应该是多平台/字节序独立的,事情会很快变得复杂。如果目标是仅相同平台的,您可以在开发协议时跳过一些原本乏味的细微差别。

就个人而言,我会选择一个简单的分隔文本行,每条记录条目。您认为通过以 bin 格式执行此操作所节省的空间可能可以忽略不计,而且同时开发阅读器和编写器几乎是微不足道的。作为奖励,您很有可能免费获得平台和字节序独立性。

祝你好运。

【讨论】:

  • 即使一个对象可简单复制,它也可能包含指向无主内存的原始指针,甚至指向同一对象内的某个位置,并且应该考虑这些指针是否会在重新加载对象时有意义,无论是由同一个进程还是另一个进程。
【解决方案2】:

对于写入和读取非 POD 类型,您确实应该为每个此类对象编写自己的序列化和反序列化函数,该函数以定义的顺序写入对象的所有成员,并按该顺序读取它们。此外,如果您希望数据可移植,则应牢记字节序。

最好也对 POD 类型使用序列化函数。有几个原因:

  • 不同的编译器/平台可能有一些不同大小的数据类型
  • 不同的编译器/平台可能以不同的顺序排列成员
  • 以上两种情况都可能导致额外的填充

在对象或其成员或 vtable 中也有潜在的指针,你不需要也不应该序列化的东西。

如果使用原始数据,所有这些都会有问题。

【讨论】:

    【解决方案3】:

    非 POD 类型(C++11 之前)或非平凡可复制类型 (C++11) 的值在二进制复制操作中不存在。这包括使用memcpy() 创建副本,或(如您所做的那样)以二进制模式写入文件并回读。此类操作(memcpy(),二进制读写)仅对 POD 类型具有定义的含义。

    std::string 不是这样的类型,因为(除其他外)它具有用户定义的构造函数、赋值运算符和管理资源(内存)的析构函数。任何包含不可复制类型实例的结构/类类型也不是可复制类型。

    【讨论】:

      【解决方案4】:

      不能写二进制,std::string 有动态内存。

      您应该将 member 写成 member,例如文本:

      file<<prod[0].name<<' '<<prod[0].code<<' '<<prod[0].price<<' '; //You must separate diferent fields with spaces
      

      并以同样的方式阅读:

      file>>prod[0].name>>prod[0].code>>prod[0].price
      

      【讨论】:

        猜你喜欢
        • 2015-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-31
        相关资源
        最近更新 更多