Serializing a class with a pointer in C ++

I want to serialize an object of type Person

. I want to use it later to save data or even save games. I know how to do that type of primitives int

, char

, bool

and even c-lines, such as char[]

.

The problem is that I want the string to be as big as required, rather than declare an array of char

size 256 and hope no one will go into something too big. I've 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 that has char*

as a member?

I realize Boost has a serialization library, but I would like to do it without using external libraries, it seems like a good job to try.

Here's my class 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);
};

      

+3


source to share


4 answers


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

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

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

The completion marker is easier to serialize. But harder to de-serialize (as you have to search ahead to find the end). Also you should avoid appearing completion marker in your object and de-serialization should be aware of escaping and remove it.

Thus, due to these complications, I prefer not to use the completion marker. As a result, I prefix the object with the size. The cost of this is that I have to encode the size of the object in such a way that it doesn't interrupt.

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 disinfect 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: for use with a 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 comments)

Helper function for string serialization

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

      

Using:

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

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

      

+3


source


You cannot serialize a pointer, you need to serialize the data pointer points.

You will need to serialize the entire network of objects, starting with Person

(or Game

) and looping through every object available from your initial object.



When deserializing, you read data from your store, allocate memory for that data, and use the address of that freshly allocated memory as a member of the object Person

/Game

+1


source


Pointer fields make it more difficult, but not impossible to serialize. If you don't want to use any serialization library, here's how you can do it.

You have to determine the size of the pointer at the time of serialization (for example, it can be a fixed size, or it can be a null-terminated C string), then you can keep the mark indicating that you are serializing the indirect object along with the size and actual content of the scope. which it points to.

When you stumble upon this mark during deserialization, you can allocate the amount of memory you want, copy the object into it, and store a pointer to the region in the deserialized object.

0


source


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

      

0


source







All Articles