Подходя к сериализации без общего базового класса - PullRequest
0 голосов
/ 07 сентября 2011

Я ищу несколько советов по проектированию для решения следующей проблемы:

Я использую геометрию буста, у меня есть пара пользовательских типов геометрии, совместимых с геометрией буста (через черты), но большинство типов яЯ использую typedefs.

class MyPoint
{
  // custom stuff
};
// declare traits for MyPoint for use wih boost geometry here

class MyTaggedPoint : public MyPoint
{
  // more custom stuff
};
// declare traits for MyTaggedPoint for use wih boost geometry here

// example typedefs
typedef boost::geometry::model::polygon<MyPoint>        Polygon;
typedef boost::geometry::model::polygon<MyTaggedPoint>  TaggedPolygon;

Моя проблема в том, когда я хочу сериализовать / десериализовать мою геометрию.

Допустим, все геометрии хранятся в двоичном поле в базе данных.Если бы у меня был базовый класс геометрии, я, вероятно, просто написал бы g-> type () (4 байта) и вызвал бы g-> save (some_outputstream) и записал бы все это в двоичное поле.Затем при чтении двоичного поля я просто прочитал бы байты и привел к соответствующему типу геометрии.

Но геометрии Boost не имеют общий базовый класс.

Как вы, ребята, обычно подходите к сериализации, когда есть несколько типов, которые могут быть сохранены в двоичном виде , и у вас нет общего базового класса ?

Я думал о том, что возможнокласс Serializer, который возвращает boost.Any, а затем геометрия может быть затем приведена к типу, который будет сохранен в (de) сериализаторе?Но тогда сериализатору потребуется метод сохранения для каждого типа геометрии?Например: Сохранить (myPolygon), Сохранить (myPoint)

Есть идеи или опыт?

Ответы [ 2 ]

2 голосов
/ 07 сентября 2011

Сериализация Boost поддерживает неинвазивную сериализацию, если вы не хотите переопределять колесо. Вы даже можете где-нибудь найти поддержку библиотек для их типов геометрии. Интерфейс несколько сложен из-за проблем с XML, к сожалению.

0 голосов
/ 13 ноября 2013

Чтобы сериализовать объекты в байты и из них, вам в конечном итоге понадобятся 2 функции для КАЖДОГО типа, который вы должны поддерживать (примитивы, объекты и т. Д.).Это «Load ()» и «Store ()».

В идеале вы используете фиксированный интерфейс для байтов - iostream, char *, некоторый буферный объект и т. Д. Для удобства чтения давайте вызовемэто «ByteBuffer», поскольку по логике именно в этом его роль.

Теперь у нас есть что-то вроде шаблонных функций для концепции Serializable:

template<typename T>
ByteBuffer Store(const T& object) { // BUT, What goes here...? }

template<typename T>
T Load(const ByteBuffer& bytes);

Хорошо, это не сработает длячто угодно, кроме примитивных типов - даже если мы сделали этих «посетителей» или что-то еще, что они буквально должны знать каждую деталь о внутренностях объекта, чтобы выполнять свою работу.Кроме того, «Load ()» является логически конструктором (на самом деле, FACTORY, поскольку он может легко потерпеть неудачу).Мы должны связать их с реальными объектами.

Чтобы сделать Serializable базовым классом, нам нужно использовать шаблон «любопытно повторяющийся шаблон».Для этого мы требуем, чтобы все производные классы имели конструктор вида:

T(const ByteBuffer& bytes);

Чтобы проверить на наличие ошибок, мы можем предоставить защищенный флаг "valid" в базовом классе, который могут установить производные конструкторы.Обратите внимание, что ваш объект все равно должен поддерживать конструкцию в заводском стиле, чтобы Load () хорошо с ним работала.

Теперь мы можем сделать это правильно, предоставив «Load» как фабрику:

template<typename T>
class Serializable // If you do reference-counting on files & such, you can add it here
{
protected:
    bool valid;

    // Require derived to mark as valid upon load
    Serializable() : valid(false) {}
    virtual ~Serializable() { valid = false; }

public:
    static T Load(const ByteBuffer& bytes); // calls a "T(bytes)" constructor

    // Store API
    virtual ByteBuffer Store() = 0;  // Interface details are up to you.
};

Теперь, просто производный от базового класса, вот так, и вы можете выбрать все, что вам нужно:

class MyObject : public Serializable<MyObject>
{
protected:
   // .. some members ...

   MyObject(const ByteBuffer& bytes)
   {
      //... Actual load logic for this object type ...
      // On success only:
      valid = true;
   }

public:
   virtual ByteBuffer Store() {
      //... store logic 
   }
};

Что здорово, что вы можете вызвать «MyObject :: Load ()», и он будет делатьименно то, что вы ожидаете.Более того, «Загрузить» можно сделать ЕДИНСТВЕННЫМ способом построения объекта, позволяя очищать API-интерфейсы для файлов, доступных только для чтения и т. Д.

Расширение этого до полных файловых API-интерфейсов требует немного больше работы, а именно добавление«Load ()», которая может читать из большего буфера (содержащего другие объекты), и «Store ()», который добавляет к существующему буферу.

В качестве примечания, НЕ используйте для этого API-интерфейсы boost.В хорошем дизайне сериализуемые объекты должны отображать 1-к-1 на упакованные структуры примитивных типов на диске - это единственный способ, которым полученные файлы действительно будут использоваться другими программами или на других машинах.Boost дает вам ужасный API, который в основном позволяет вам делать то, о чем вы пожалеете позже.

...