Serializing a class with a pointer in C++

こ雲淡風輕ζ 提交于 2019-12-23 17:13:57

问题


I want to serialize an object of type Person. I want to use it later on for data saving or even game saving. I know how to do it for primitives like int, char, bool, and even c-strings like char[].

The problem is, I want the string to be as big as it needs to rather than declaring a char array of size 256 and hoping no one enters something too big. I read that serializing a class with std::string as a member doesn't work because it has an internal pointer, but is there a way to serialize my class which has a char* as a member?

I realize Boost has a serialization library, but I'd like to do this without the need of external libraries, it seems like a good activity to try.

Here's my Person class:

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

回答1:


First: Use std::string in your class it will make your life so much easier in the long run.

But this advice works for both std::string and char* (with minor tweaks that should be obvious).

Basically you want to serialize data of unknown size (at compile time). This means when you de-serialize the data you must either have a technique that tells you how long the data is (prefix the object with a size) or a way to find the end of the data (a termination marker).

A termination marker is easier for serialization. But harder for de-serialization (as you must seek forward to find the end). Also you must escape any occurrences of the termination marker within your object and the de-serialization must know about the escaping and remove it.

Thus because of this complications I prefer not to use a termination marker. As a result I prefix the object with a size. The cost of this is that I must encode the size of the object in a way that will not break.

So if we prefix an object with its size you can do this:

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

Then you can de-serialize like this:

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)

Edit 1 (based on comments) Update: to use with string:

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

Edit 2 (based on commented)

Helper function to serialize a string

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

Serialize a Person

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

Usage:

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

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



回答2:


You can't serialize pointer, you need to serialize data pointer points to.

You'll need to serialize whole web of objects, starting from Person (or Game) and looking into each object, which is reachable from your start object.

When deserializing, you reading data from your storage, allocate memory for that data and use address of this freshly allocated memory as a member of Person/Game object




回答3:


Pointer fields make it bit harder, but not impossible to serialize. If you don't want to use any of the serialization libraries, here is how you can do it.

You should determine the size of what is pointed to at the time of serialization (e.g. it may be of fixed size or it may be a C-string with null character at the end), then you can save a mark indicating that you're serializing an indirect object together with size and the actual content of the area pointed to.

When you stumble upon that mark during deserialization, you can allocate the right amount of memory, copy the object into it and store the pointer to the area in the deserialized object.




回答4:


I recommend using a vector to encapsulate strings for serialization.

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


来源:https://stackoverflow.com/questions/9778806/serializing-a-class-with-a-pointer-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!