【问题标题】:Serializing a class with a pointer in C++用 C++ 中的指针序列化一个类
【发布时间】:2012-03-19 22:18:38
【问题描述】:

我想序列化Person 类型的对象。我以后想用它来保存数据甚至保存游戏。我知道如何为int、char、bool 之类的原语,甚至char[] 之类的 c 字符串执行此操作。

问题是,我希望字符串尽可能大,而不是声明一个大小为 256 的 char 数组并希望没有人输入太大的东西。我读到用std::string 作为成员序列化一个类是行不通的,因为它有一个内部指针,但是有没有办法序列化我的以char* 作为成员的类?

我知道 Boost 有一个序列化库,但我想这样做而不需要外部库,这似乎是一个很好的尝试。

这是我的Person 课程:

class Person
{
private:
   char* _fname; 
   char* _lname;

public:
   Person();
   Person(const char* fname, const char* lname);
   Person(const string& fname, const string& lname);

   string fname() const;
   void fname(const char* fname);
   void fname(const string& fname);

   string lname() const;
   void lname(const char* lname);
   void lname(const string& lname);
};

【问题讨论】:

  • 为什么一开始就有裸指针?你喜欢疼痛吗?
  • 我会喜欢使用std::string,但有人告诉我,用它作为成员序列化一个类可能非常棘手。 char 数组我想也可以,但你必须在编译时知道长度。
  • 很遗憾,信仰和信仰的问题我帮不上忙,但是如果我要和我喜欢的人谈论 C++,我会尽力让他们使用std::string。跨度>
  • 有没有可能用std::string完成序列化呢,别误会,我真的想用它,我喜欢std::string,但它没有似乎不起作用。
  • Loki 的回答对我来说非常理智......我的课程在我需要整个图书馆的情况下并没有那么那么大。此外,我喜欢自己使用标准语言功能,这样库看起来并不神奇。

标签: c++ serialization pointers


【解决方案1】:

首先:在你的课堂上使用 std::string 从长远来看,它会让你的生活变得更轻松。

但这个建议对 std::string 和 char* 都有效(稍作调整应该很明显)。

基本上你想序列化未知大小的数据(在编译时)。这意味着当您对数据进行反序列化时,您必须拥有一种技术来告诉您数据有多长(以大小为对象添加前缀)或找到数据结尾的方法(终止标记)。

终止标记更容易进行序列化。但反序列化更难(因为您必须向前寻找结尾)。此外,您必须转义对象中出现的任何终止标记,并且反序列化必须知道转义并将其删除。

因此,由于这种复杂性,我不想使用终止标记。结果,我在对象前面加上了一个大小。这样做的代价是我必须以不会破坏的方式对对象的大小进行编码。

所以如果我们在一个对象前面加上它的大小,你可以这样做:

// Place a ':' between the string and the size.
// There must be a marker as >> will continue reading if
// fname contains a digit as its first character.
// I don;t like using a space as >> skips spaces if you are not carefull
// and it is hard to tell the start of the string if the first characters in fname
// are the space character.
std::cout << strlen(fname) << ":" << fname;

然后你可以像这样反序列化:

size_t size;
char   mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{    throw BadDataException;
}
result = new char[size+1]();  // Note the () to zero fill the array.
std::cin.read(result, size)

编辑 1(基于 cmets)更新:与字符串一起使用:

size_t size;
char   mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{    throw BadDataException;
}
std::string  result(' ', size);  // Initialize string with enough space.
std::cin.read(&result[0], size)  // Just read directly into the string

编辑 2(基于评论)

序列化字符串的辅助函数

struct StringSerializer
{
    std::string&    value;
    StringSerializer(std::string const& v):value(const_cast<std::string&>(v)){}
    friend std::ostream& operator<<(std::ostream& stream, StringSerializer const& data)
    {
        stream << data.value.size() << ':' << data.value;
    }
    friend std::istream& operator>>(std::istream& stream, StringSerializer const& data)
    {
        std::size_t size;
        char        mark(' ');
        stream >> size >> mark;
        if (!stream || mark != ':')
        {    stream.setstate(std::ios::badbit);
             return stream;
        }
        data.value.resize(size);
        stream.read(&data.value[0], size);
    }
};

序列化一个人

std::ostream& operator<<(std::ostream& stream, Person const& data)
{
    return stream << StringSerializer(data.fname) << " "
                  << StringSerializer(data.lname) << " "
                  << data.age                     << "\n";
}
std::istream& operator>>(std::istream& stream, Person& data)
{
    stream    >> StringSerializer(data.fname)
              >> StringSerializer(data.lname)
              >> data.age;
    std::string line;
    std::getline(stream, line);

    if (!line.empty())
    {    stream.setstate(std::ios::badbit);
    }
    return stream;
}

用法:

int main()
{
    Person p;
    std::cin  >> p;
    std::cout << p;

    std::ofstream  f("data");
    f << p;
}

【讨论】:

  • 应该将cin 和cout 替换为fstream 对象吗?而这只是一个笼统的问题,在提取size 和mark 时,它是如何知道读取数字和字符的?它们在文件中的表示方式是否不同?
  • std::cin 和 std::cout 只是流的示例。它们可以被任何其他流类型对象替换,包括 std::fstream。
  • 流对象查看它们流入/流出的对象类型并执行正确的操作。您需要使用空格字符分隔流中的对象,以便 >> 运算符知道每个开始/结束的位置。注意:&lt;size&gt;:&lt;string data&gt; 是一个对象,因此内部没有空格分隔,但应该与其他对象空格分隔。
  • 太棒了,它成功了!是的,我会改用std::string。我将只使用str.length() 而不是strlen() 并创建另一个string,结果如下:string strResult(result)。谢谢!
  • @rcplusplus:不,不要那样做。确保字符串大小正确,然后直接读入字符串。
【解决方案2】:

不能序列化指针,需要序列化指向的数据指针。

您需要序列化整个对象网络,从Person(或Game)开始并查看每个对象,这些对象可从您的起始对象访问。

反序列化时,您从存储中读取数据,为该数据分配内存并使用这个新分配的内存地址作为Person/Game对象的成员

【讨论】:

  • 如何序列化char*指向的数据?请原谅我问,我对指针不太熟练,但不取消引用指针(如*_lname)只是返回第一个字符?
  • 在 C 中 char* 是表示“字符串”的标准方式:_lname 指向第一个字符,字符串一直持续到遇到第一个零字节。在 C++ 中最好使用std::string 来表示“字符串”,但是在序列化时仍然需要通过std::string::c_str() 获取指向第一个字符的指针。
【解决方案3】:

指针字段使它更难序列化,但并非不可能序列化。如果您不想使用任何序列化库,请按照以下方法操作。

您应该确定序列化时指向的内容的大小(例如,它可能是固定大小,也可能是末尾带有空字符的 C 字符串),然后您可以保存一个标记,表明您正在序列化一个间接对象以及所指向区域的大小和实际内容。

当您在反序列化过程中偶然发现该标记时,您可以分配适量的内存,将对象复制到其中并将指向该区域的指针存储在反序列化对象中。

【讨论】:

    【解决方案4】:

    我建议使用向量来封装字符串 序列化。

    #include <vector>
    using namespace std;
    map vector<unsigned char> cbuff;
    inline cbuff vchFromString(const std::string &str) {
    unsigned char *strbeg = (unsigned char*) str.c_str();
      return cbuff(strbeg, strbeg + str.size());
    }
    inline std::string stringFromVch(const cbuff &vch) {
     std::string res;
     std::vector<unsigned char>::const_iterator vi = vch.begin();
     while (vi != vch.end()) {
      res += (char) (*vi);
      vi++;
     }
     return res;
    }
    
    class Example
    {
      cbuff label;
      Example(string labelIn)
      {
        SetLabel(labelIn);
      }
      IMPLEMENT_SERIALIZE
      (
        READWRITE(label);
      )
      void SetLabel(string labelIn)
      {
        label = vchFromString(labelIn);
      }
      string GetLabel()
      {
        return (stringFromVch(label));
      }
    };
    

    【讨论】:

      猜你喜欢
      • 2018-12-13
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-01
      • 2015-06-19
      • 1970-01-01
      相关资源
      最近更新 更多