Сериализация класса с указателем в C ++ - PullRequest
3 голосов
/ 20 марта 2012

Я хочу сериализовать объект типа Person.Я хочу использовать его позже для сохранения данных или даже для сохранения игр.Я знаю, как это сделать для таких примитивов, как int, char, bool и даже c-строк, таких как char[].

Проблема в том, что я хочу, чтобы строка была настолько большой, насколько это необходимо, вместо того, чтобы объявлять массив char размером 256 и надеяться, что никто не введет что-то слишком большое.Я читал, что сериализация класса с std::string в качестве члена не работает, потому что он имеет внутренний указатель, но есть ли способ сериализации моего класса, который имеет char* в качестве члена?

IЯ понимаю, что 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);
};

Ответы [ 4 ]

3 голосов
/ 20 марта 2012

Во-первых: используйте 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 (на основе комментариев) Обновление: использовать со строкой:

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;
}
1 голос
/ 20 марта 2012

Вы не можете сериализовать указатель, вам нужно сериализовать указатель данных на.

Вам нужно будет сериализовать всю сеть объектов, начиная с Person (или Game) и просматривая каждый объект, который доступен из вашего стартового объекта.

При десериализации вы читаете данные из своего хранилища, выделяете память для этих данных и используете адрес этой вновь выделенной памяти в качестве члена Person / Game объекта

0 голосов
/ 14 июля 2016

Я рекомендую использовать вектор для инкапсуляции строк для сериализации.

#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 голосов
/ 20 марта 2012

Поля указателя делают его немного сложнее, но не невозможно сериализовать.Если вы не хотите использовать какую-либо из библиотек сериализации, вот как вы можете это сделать.

Вы должны определить размер того, на что указывают во время сериализации (например, он может быть фиксированнымsize или это может быть C-строка с нулевым символом в конце), затем вы можете сохранить метку, указывающую, что вы сериализуете косвенный объект вместе с размером и фактическим содержимым области, на которую указывает.

Когда вы наткнетесь на эту метку во время десериализации, вы можете выделить нужный объем памяти, скопировать в него объект и сохранить указатель на область в десериализованном объекте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...