serialize / deserialize
От: Ignoramus  
Дата: 06.12.05 08:37
Оценка:
Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)

Задумался

Подскажите плз, если можно, то как, или это гон?
Re: serialize / deserialize
От: Greg Zubankov СССР  
Дата: 06.12.05 08:43
Оценка: 3 (2)
Здравствуйте, Ignoramus, Вы писали:

I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)

I>Задумался
I>Подскажите плз, если можно, то как, или это гон?

Для этого надо единообразно трактовать эти операции.
Вот пример из Boost.Serialization, где обе эти операции совмещены в одной функции:

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }


Когда класс Архив соответствует "output", оператор & определен как оператор <<. И наоборот.
Re: serialize / deserialize
От: Sergey Россия  
Дата: 06.12.05 08:52
Оценка: 2 (1)
Здравствуйте, Ignoramus, Вы писали:

I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)


I>Задумался


I>Подскажите плз, если можно, то как, или это гон?


С помощью boost::serialization это делается примерно так:

class Foo
{
  int x_;
  std::string name_;
  template <class Archive> void serialize(Archive &ar, const unsigned int ver)
  {
    Ar & x_;
    Ar & name_;
  }
....
};

void foo(boost::serialization::text_iarchive &a)
{
  Foo x;
  a & x;
}


оператор & у iarchive читает, у oarchive — пишет. Просто объединили >> и << под одним именем. Зато нельзя сделать io_archive, но он обычно и не нужен.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: serialize / deserialize
От: Шахтер Интернет  
Дата: 06.12.05 10:58
Оценка: +4
Здравствуйте, Ignoramus, Вы писали:

I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)


I>Задумался


I>Подскажите плз, если можно, то как, или это гон?


Можно, но это имеет смысл только для простых случаев.
Потому что при десериализации сложного класса необходимо делать проверки на корректность и консистентность считанных параметров.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: serialize / deserialize
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 06.12.05 11:09
Оценка: +1
Здравствуйте, Ignoramus, Вы писали:

I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)


I>Подскажите плз, если можно, то как, или это гон?


Это не гон, а вполне возможная вещь, которую уже продемонстрировали: Re: serialize / deserialize
Автор: Greg Zubankov
Дата: 06.12.05
, Re: serialize / deserialize
Автор: Sergey
Дата: 06.12.05
.
Но, как правильно заметил
Автор: Шахтер
Дата: 06.12.05
Шахтер, все не так просто. Кроме проверки значений на корректность есть еще и проблема версионности. Если формат поддерживает версионность, то процедура сериализации должна записать версию схемы данных. А процедура десериализации должна прочитать версию данных, затем, в соответствии с версией что-то прочитать, что-то пропустить. А не прочитанные данные как-то проинициализировать значениями по-умолчанию. В одной процедуре сделать это будет сложновато, имхо.
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: serialize / deserialize
От: Кодёнок  
Дата: 06.12.05 11:45
Оценка:
Здравствуйте, eao197, Вы писали:

I>>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)

I>>Подскажите плз, если можно, то как, или это гон?

E>Кроме проверки значений на корректность есть еще и проблема версионности. Если формат поддерживает версионность, то процедура сериализации должна записать версию схемы данных. А процедура десериализации должна прочитать версию данных, затем, в соответствии с версией что-то прочитать, что-то пропустить. А не прочитанные данные как-то проинициализировать значениями по-умолчанию. В одной процедуре сделать это будет сложновато, имхо.


Если данные действительно простые (просто вложенные структуры), то отнюдь не сложно:

class Foo
{
  int x_;
  std::string name_;

  Foo() 
   : x_(33)
   , name_("Unknown")
  {
  }
  template <class Serializer> void serialize(Serializer& se, const unsigned int ver)
  {
    se[101] & x_;
    se[102] & name_;
  }
....
};


В случае отсутствия в загружаемом потоке поля, помеченного номером "102" (например, поток был создан предыдущей версией, где этого поля не было), будет использовано значение по умолчанию ("Unknown"), которое может задаваться либо в конструкторе, либо в самой процедуре [де]сериализации.

Типичный пример — конфиги; там это прокатывает на ура. Можно удалять и добавлять поля почти свободно
Re[2]: serialize / deserialize
От: Глеб Алексеев  
Дата: 06.12.05 12:08
Оценка:
Здравствуйте, eao197, Вы писали:

E> Если формат поддерживает версионность, то процедура сериализации должна записать версию схемы данных. А процедура десериализации должна прочитать версию данных, затем, в соответствии с версией что-то прочитать, что-то пропустить. А не прочитанные данные как-то проинициализировать значениями по-умолчанию. В одной процедуре сделать это будет сложновато, имхо.


Сразу оговорюсь, что сериализация — не мой конек, да и boost::serialization пробовал только на простых примерах, но вроде поддержка версионности там встроенная:
#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>

class bus_route
{
    friend class boost::serialization::access;
    std::list<bus_stop *> stops;
    std::string driver_name;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        // only save/load driver_name for newer archives
        if(version > 0)
            ar & driver_name;
        ar & stops;
    }
public:
    bus_route(){}
};

BOOST_CLASS_VERSION(bus_route, 1)

http://boost.org/libs/serialization/doc/tutorial.html#versioning
... << RSDN@Home 1.2.0 alpha rev. 619>>
Re[3]: serialize / deserialize
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 06.12.05 12:09
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>Если данные действительно простые (просто вложенные структуры), то отнюдь не сложно:


<...код поскипан...>

Кё>В случае отсутствия в загружаемом потоке поля, помеченного номером "102" (например, поток был создан предыдущей версией, где этого поля не было), будет использовано значение по умолчанию ("Unknown"), которое может задаваться либо в конструкторе, либо в самой процедуре [де]сериализации.


Вот этот момент по-подробнее, пожалуйста
Есть у нас код:
Foo f;
f.set_name( "eao197" );

iarchive instream( "datafile.dat" );
// Предполагается, что как-то запустится f.serialize.
instream >> f;

assert( f.get_name() == "eao197" ); // ???


Кё>Типичный пример — конфиги; там это прокатывает на ура. Можно удалять и добавлять поля почти свободно


А вот не типичный пример: http://eao197.narod.ru/objessty/html/oess_1_2_0__extensible_types.html
Как сериализация/десериализация таких не сложных типов будет выглядеть в одной процедуре?
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: serialize / deserialize
От: Angler Россия  
Дата: 06.12.05 12:09
Оценка:
Здравствуйте, Шахтер, Вы писали:


Ш>Потому что при десериализации сложного класса необходимо делать проверки на корректность и консистентность считанных параметров.


Если посмотреть на проблему немного поглубже, то все можно свести к простому случаю, при наличии сервисов предварительной конвертации персистентных данных к текущей версии читалки. Например так сделано в Adobe InDesign SDK. К плюсам данного метода можно отнести:
1. процедура чтения/записи персистентных данных тривиальна
2. возможность посредством schemata задавать форматы персистентных данных от версии к версии:

//5.1 --> 5.2 : удалено поле kcxmtTagValueField, добавлено поле kcxmtNumField числового типа
resource SchemaList(1)
{{
    Schema // implies ImplementationSchema
    {
        kcxmtDataTagTextAttribImpl,
        {5, 1}, //format number
        {
            { PMString{ kcxmtTagNameField, ""} }
            { PMString{ kcxmtTagValueField, ""} }
        }
    },
    
    Schema // implies ImplementationSchema
    {
        kcxmtDataTagTextAttribImpl,
        {5, 2}, //format number
        {
            { Int16{ kcxmtNumField, 1} }
            { PMString{kcxmtTagNameField, ""} }
        }
    }
}};


При фундаментальном изменении формата персистентных данных использование schemata может быть затруднено, в данном случае очень просто самому реализовать конвертор из версии X в версию Y. Вот интерфейс:
//читаем данные в старом формате из inStream, конвертируем и пишем их в outStream
virtual ImplementationID ConvertTag(..., IPMStream *inStream, IPMStream *outStream, ...)
Re[3]: serialize / deserialize
От: Шахтер Интернет  
Дата: 06.12.05 12:45
Оценка:
Здравствуйте, Angler, Вы писали:

Вот простейший случай.

class RepeatCounter
 {
   static const unsigned MinCounter=10;
   static const unsigned MaxCounter=1000

   static const unsigned DefaultCounter=100;

   unsigned counter;

  public:

   RepeatCounter() : counter(DefaultCounter) {}

   template <class Dev>
   void serialize(Dev &dev) const
    {
     dev << counter ; 
    }

   template <class Dev>
   void deserialize(Dev &dev)
    {
     dev >> counter ; 

     if( counter<MinCounter || counter>MaxCounter )
       throw deserialization_error("class RepeatCounter : out of bound");
    }
 };


Как здесь обойтись одним общим методом (одно решение я знаю, но подсказывать не буду)?
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: serialize / deserialize
От: Ignoramus  
Дата: 06.12.05 12:47
Оценка:
А что если операция сохранения поля некоторого типа параметризована дополнительными данными?

Т.е. нельзя написать

os << data;


а можно только

serialize( os, data, somedata );
// or
data.seriazlize( os, somedata );


Тогда boost::serialization может быть использован?
Re[4]: serialize / deserialize
От: Кодёнок  
Дата: 06.12.05 12:48
Оценка:
Здравствуйте, eao197, Вы писали:

E>Вот этот момент по-подробнее, пожалуйста

E>f.set_name( "eao197" );
E>instream >> f;
E>assert( f.get_name() == "eao197" ); // pass

Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.

Кё>>Типичный пример — конфиги; там это прокатывает на ура. Можно удалять и добавлять поля почти свободно


E>А вот не типичный пример: http://eao197.narod.ru/objessty/html/oess_1_2_0__extensible_types.html

E>Как сериализация/десериализация таких не сложных типов будет выглядеть в одной процедуре?

Под сложносью типа я имел ввиду не только уровни вложенноси и взаимосвязи между атрибутами, но и то, какие именно изменения ожидаются в структурах данных между версиями. Если совершенно точно будет только добавление и удаление атрибутов, то использовать решения с DDL будет лишним усложнением При появлении дополнительных условий вроде "сериализуется только, если в нем есть значения" или "атрибут может поменять тип" сложность моего решения растёт очень быстро, так что сравнивать смысла нет; как я сказал, оно для частных, простых случаев.

Версия 1.
handshake_t::operator &(Binder& b)
{
  m_version     & b[100];
  m_client_id   & b[101];
}


Версия 2.
signature_setup_t::operator & (Binder& b)
{
  m_supported   & b[200];
  m_preferred   & b[201]; 
  // прикрутить "только если есть значения" можно, 
  // но уже не очень красиво будет
}
compression_setup_t::operator & (Binder& b)
{
  m_supported   & b[300];
  m_preferred   & b[301]; 
  m_level       & b[302];
}
handshake_t::operator &(Binder& b)
{
  m_version     & b[100];
  m_client_id   & b[101];
  m_signature   & b[102];
  m_compression & b[103];
}


Код
Binder b;
m_handshake & b;
cin >> b;
cout << b.count_length_required_to_save();
cout << b;


Я переименовал "Binder" потому что сериализация отложена, а операция & лишь связывает адрес памяти с процедурами загрузки/выгрузки...
Re[5]: serialize / deserialize
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 06.12.05 12:55
Оценка:
Здравствуйте, Кодёнок, Вы писали:

E>>Вот этот момент по-подробнее, пожалуйста

E>>f.set_name( "eao197" );
E>>instream >> f;
E>>assert( f.get_name() == "eao197" ); // pass

Кё>Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.


У нас нет значения по умолчанию. Я его изменил методом set_name(). Как метод serialize мне установит значение атрибута name_. Интересует именно код.

Кё>Версия 2.

Кё>
Кё>signature_setup_t::operator & (Binder& b)
Кё>{
Кё>  m_supported   & b[200];
Кё>  m_preferred   & b[201]; 
Кё>  // прикрутить "только если есть значения" можно, 
Кё>  // но уже не очень красиво будет
Кё>}
Кё>


Тем не менее, было бы интересно посмотреть на реализацию "только если есть значения".
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[4]: serialize / deserialize
От: Angler Россия  
Дата: 06.12.05 12:56
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Как здесь обойтись одним общим методом (одно решение я знаю, но подсказывать не буду)?


void ReadWrite(IStream *stream)
{
  stream->ReadWrite(counter);
  if(stream->IsReading())
  {
    checkInvariant();
  }
}

void checkInvariant()
{
  if(counter < MinCounter || counter > MaxCounter)
    throw std::range_error("class RepeatCounter : out of bound");        
}

Re[4]: serialize / deserialize
От: Angler Россия  
Дата: 06.12.05 13:02
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Как здесь обойтись одним общим методом (одно решение я знаю, но подсказывать не буду)?


Как более простой вариант — указывать в schema диапозон возможных значений
Re[6]: serialize / deserialize
От: Кодёнок  
Дата: 06.12.05 13:23
Оценка:
Здравствуйте, eao197, Вы писали:

Кё>>Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.


E>У нас нет значения по умолчанию. Я его изменил методом set_name(). Как метод serialize мне установит значение атрибута name_. Интересует именно код.


Это поведение не задается (явно) никаким кодом. Могу привести цикл чтения троек ID/длина/данные, из которого ничего не следует

Еще раз: если при загрузке поле с номером 102 встретится, то произойдет переписывание атрибута name_. Если № 102 не встретится, то name_ останется незатронутым. Насколько я могу судить из следующей цитаты, твоя система работает точно так же:

Значения по-умолчанию для атрибутах в расширениях
При описании в DDL атрибутов в расширениях очень важно указывать значение по-умолчанию для каждого такого атрибута. Дело в том, что при десериализации объекта ObjESSty назначает значения только тем атрибутам, которые были обнаружены в сериализованном представлении. Остальные атрибуты остаются без изменения. Это может стать проблемой, например, в таком случае: объекту A было присвоено какое-то значение. Затем в объект A была осуществлена десериализация некоторого двоичного представления. Если двоичное представление было создано предыдущей версией, то в объекте A часть атрибутов получат новые значения, но атрибуты из последнего расширения изменены не будут. Т.е. объект A не будет корректным образом объекта из двоичного представления, поскольку отсутствовавшие в этом представлении атрибуты не получили значений по-умолчанию.


Кё>> m_preferred & b[201];

Кё>> // прикрутить "только если есть значения" можно,
Кё>> // но уже не очень красиво будет
E>Тем не менее, было бы интересно посмотреть на реализацию "только если есть значения".

Её нет, потому что мне не требовалось Сделать можно по-разному, например, так:

bool std_string_not_empty(std::string& val, bool& skip)
{
  skip = val.empty();
  return true; // success
}
{
  m_preferred   & b[201];
  b[201].on_save = std_string_not_empty;
}
Re[7]: serialize / deserialize
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 06.12.05 13:34
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>>>Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.


E>>У нас нет значения по умолчанию. Я его изменил методом set_name(). Как метод serialize мне установит значение атрибута name_. Интересует именно код.


Кё>Это поведение не задается (явно) никаким кодом. Могу привести цикл чтения троек ID/длина/данные, из которого ничего не следует


Кё>Еще раз: если при загрузке поле с номером 102 встретится, то произойдет переписывание атрибута name_. Если № 102 не встретится, то name_ останется незатронутым.


Ну и как нам вернуть его значение по умолчанию, если мы этого хотим?
Моя система как раз позволяет явно это задать для конкретного атрибута.
А как быть, если процедура сериализации/десериализации пишется вручную, да еще и одна для двух операций?

Кё>
Кё>bool std_string_not_empty(std::string& val, bool& skip)
Кё>{
Кё>  skip = val.empty();
Кё>  return true; // success
Кё>}
Кё>{
Кё>  m_preferred   & b[201];
Кё>  b[201].on_save = std_string_not_empty;
Кё>}
Кё>


А как из этого понять, что on_save должно вызывать std_string_not_empty именно для m_preffered? Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[8]: serialize / deserialize
От: Кодёнок  
Дата: 06.12.05 14:49
Оценка: 13 (1)
Здравствуйте, eao197, Вы писали:

E>Ну и как нам вернуть его значение по умолчанию, если мы этого хотим?

E>Моя система как раз позволяет явно это задать для конкретного атрибута.

А, теперь я понял наконец Нужна возможность намеренно пропустить (не загружать) один из атрибутов, или возможность уже после загрузки при выполнении некоторых условий вернуть его в значение по умолчанию?

Такой задачи тоже не было. Если первое, то можно сделать аналогично (b[201].on_load), если второе, то наверное просто константу/define, которую использовать и в конструкторе и при возвращении значения по умолчанию:

struct Foo
{
  std::string name;
  static std::string name_default()
  {
    static const char name[] = "Jah Murder";
    return name;
  }
  Foo() : name(name_default()) {}
}


E>А как быть, если процедура сериализации/десериализации пишется вручную, да еще и одна для двух операций?


В смысле?

E>А как из этого понять, что on_save должно вызывать std_string_not_empty именно для m_preffered?


Так ведь операция & выполняется каждый раз при загрузке/сохранении для конкретного экземпляра объекта. Предполагается, что сериализатор (Binder) для каждого экземпляра объекта индивидуально создаётся, чтобы один раз выполнить загрузку или сохранение.

E>Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?


...атрибутов с идентификатором 201... Вместо числа можно использовать их имя — "name", "x" и т.п., число компактней.
Re[9]: serialize / deserialize
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 06.12.05 14:56
Оценка:
Здравствуйте, Кодёнок, Вы писали:

E>>Ну и как нам вернуть его значение по умолчанию, если мы этого хотим?

E>>Моя система как раз позволяет явно это задать для конкретного атрибута.

Кё>А, теперь я понял наконец Нужна возможность намеренно пропустить (не загружать) один из атрибутов, или возможность уже после загрузки при выполнении некоторых условий вернуть его в значение по умолчанию?


Кё>Такой задачи тоже не было.


Не было. Но если бы была, то делать это в одной процедуре было бы не очень удобно.

Кё> Если первое, то можно сделать аналогично (b[201].on_load), если второе, то наверное просто константу/define, которую использовать и в конструкторе и при возвращении значения по умолчанию:


E>>Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?


Кё>...атрибутов с идентификатором 201... Вместо числа можно использовать их имя — "name", "x" и т.п., число компактней.


Теперь понял. Я думал, что речь идет о номере версии.




Вообще меня повело куда-то. Спасибо за ответы.
Я просто хотел сказать, что ручное формирование функции сериализации/десериализации при наличии опциональных атрибутов, проверки условий (как в примерах Шахтера) и наличием разных версий -- это тот еще геморрой. Который лучше переложить на плечи генератора кода.
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[5]: serialize / deserialize
От: Шахтер Интернет  
Дата: 07.12.05 09:20
Оценка:
Здравствуйте, Angler, Вы писали:

A>Здравствуйте, Шахтер, Вы писали:


Ш>>Как здесь обойтись одним общим методом (одно решение я знаю, но подсказывать не буду)?


A>Как более простой вариант — указывать в schema диапозон возможных значений


Это не прокатит, если условие более сложное, чем принадлежность диапазону.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.