Здравствуйте, 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", оператор & определен как оператор <<. И наоборот.
Здравствуйте, 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 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Ignoramus, Вы писали:
I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)
I>Задумался
I>Подскажите плз, если можно, то как, или это гон?
Можно, но это имеет смысл только для простых случаев.
Потому что при десериализации сложного класса необходимо делать проверки на корректность и консистентность считанных параметров.
Здравствуйте, Ignoramus, Вы писали:
I>Случайно услышал на другом форуме, что serialization/deserialization вроде бы можно делать в одной функции (а не в двух)
I>Подскажите плз, если можно, то как, или это гон?
Шахтер, все не так просто. Кроме проверки значений на корректность есть еще и проблема версионности. Если формат поддерживает версионность, то процедура сериализации должна записать версию схемы данных. А процедура десериализации должна прочитать версию данных, затем, в соответствии с версией что-то прочитать, что-то пропустить. А не прочитанные данные как-то проинициализировать значениями по-умолчанию. В одной процедуре сделать это будет сложновато, имхо.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, 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"), которое может задаваться либо в конструкторе, либо в самой процедуре [де]сериализации.
Типичный пример — конфиги; там это прокатывает на ура. Можно удалять и добавлять поля почти свободно
Здравствуйте, 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 archivesif(version > 0)
ar & driver_name;
ar & stops;
}
public:
bus_route(){}
};
BOOST_CLASS_VERSION(bus_route, 1)
Здравствуйте, Кодёнок, Вы писали:
Кё>Если данные действительно простые (просто вложенные структуры), то отнюдь не сложно:
<...код поскипан...>
Кё>В случае отсутствия в загружаемом потоке поля, помеченного номером "102" (например, поток был создан предыдущей версией, где этого поля не было), будет использовано значение по умолчанию ("Unknown"), которое может задаваться либо в конструкторе, либо в самой процедуре [де]сериализации.
Вот этот момент по-подробнее, пожалуйста
Есть у нас код:
Ш>Потому что при десериализации сложного класса необходимо делать проверки на корректность и консистентность считанных параметров.
Если посмотреть на проблему немного поглубже, то все можно свести к простому случаю, при наличии сервисов предварительной конвертации персистентных данных к текущей версии читалки. Например так сделано в Adobe InDesign SDK. К плюсам данного метода можно отнести:
1. процедура чтения/записи персистентных данных тривиальна
2. возможность посредством schemata задавать форматы персистентных данных от версии к версии:
При фундаментальном изменении формата персистентных данных использование schemata может быть затруднено, в данном случае очень просто самому реализовать конвертор из версии X в версию Y. Вот интерфейс:
//читаем данные в старом формате из inStream, конвертируем и пишем их в outStream
virtual ImplementationID ConvertTag(..., IPMStream *inStream, IPMStream *outStream, ...)
Здравствуйте, 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 будет лишним усложнением При появлении дополнительных условий вроде "сериализуется только, если в нем есть значения" или "атрибут может поменять тип" сложность моего решения растёт очень быстро, так что сравнивать смысла нет; как я сказал, оно для частных, простых случаев.
Здравствуйте, Кодёнок, Вы писали:
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++.
Здравствуйте, eao197, Вы писали:
Кё>>Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.
E>У нас нет значения по умолчанию. Я его изменил методом set_name(). Как метод serialize мне установит значение атрибута name_. Интересует именно код.
Это поведение не задается (явно) никаким кодом. Могу привести цикл чтения троек ID/длина/данные, из которого ничего не следует
Еще раз: если при загрузке поле с номером 102 встретится, то произойдет переписывание атрибута name_. Если № 102 не встретится, то name_ останется незатронутым. Насколько я могу судить из следующей цитаты, твоя система работает точно так же:
Значения по-умолчанию для атрибутах в расширениях
При описании в DDL атрибутов в расширениях очень важно указывать значение по-умолчанию для каждого такого атрибута. Дело в том, что при десериализации объекта ObjESSty назначает значения только тем атрибутам, которые были обнаружены в сериализованном представлении. Остальные атрибуты остаются без изменения. Это может стать проблемой, например, в таком случае: объекту A было присвоено какое-то значение. Затем в объект A была осуществлена десериализация некоторого двоичного представления. Если двоичное представление было создано предыдущей версией, то в объекте A часть атрибутов получат новые значения, но атрибуты из последнего расширения изменены не будут. Т.е. объект A не будет корректным образом объекта из двоичного представления, поскольку отсутствовавшие в этом представлении атрибуты не получили значений по-умолчанию.
Кё>> m_preferred & b[201]; Кё>> // прикрутить "только если есть значения" можно, Кё>> // но уже не очень красиво будет E>Тем не менее, было бы интересно посмотреть на реализацию "только если есть значения".
Её нет, потому что мне не требовалось Сделать можно по-разному, например, так:
Здравствуйте, Кодёнок, Вы писали:
Кё>>>Да, если поля "name" (№ 102) в потоке нет, загрузка его не затронет и у нас останется значение по умолчанию. Как вариант, значение по умолчанию может присвоить сама процедура загрузки.
E>>У нас нет значения по умолчанию. Я его изменил методом set_name(). Как метод serialize мне установит значение атрибута name_. Интересует именно код.
Кё>Это поведение не задается (явно) никаким кодом. Могу привести цикл чтения троек ID/длина/данные, из которого ничего не следует
Кё>Еще раз: если при загрузке поле с номером 102 встретится, то произойдет переписывание атрибута name_. Если № 102 не встретится, то name_ останется незатронутым.
Ну и как нам вернуть его значение по умолчанию, если мы этого хотим?
Моя система как раз позволяет явно это задать для конкретного атрибута.
А как быть, если процедура сериализации/десериализации пишется вручную, да еще и одна для двух операций?
Кё>
А как из этого понять, что on_save должно вызывать std_string_not_empty именно для m_preffered? Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Ну и как нам вернуть его значение по умолчанию, если мы этого хотим? E>Моя система как раз позволяет явно это задать для конкретного атрибута.
А, теперь я понял наконец Нужна возможность намеренно пропустить (не загружать) один из атрибутов, или возможность уже после загрузки при выполнении некоторых условий вернуть его в значение по умолчанию?
Такой задачи тоже не было. Если первое, то можно сделать аналогично (b[201].on_load), если второе, то наверное просто константу/define, которую использовать и в конструкторе и при возвращении значения по умолчанию:
E>А как быть, если процедура сериализации/десериализации пишется вручную, да еще и одна для двух операций?
В смысле?
E>А как из этого понять, что on_save должно вызывать std_string_not_empty именно для m_preffered?
Так ведь операция & выполняется каждый раз при загрузке/сохранении для конкретного экземпляра объекта. Предполагается, что сериализатор (Binder) для каждого экземпляра объекта индивидуально создаётся, чтобы один раз выполнить загрузку или сохранение.
E>Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?
...атрибутов с идентификатором 201... Вместо числа можно использовать их имя — "name", "x" и т.п., число компактней.
Здравствуйте, Кодёнок, Вы писали:
E>>Ну и как нам вернуть его значение по умолчанию, если мы этого хотим? E>>Моя система как раз позволяет явно это задать для конкретного атрибута.
Кё>А, теперь я понял наконец Нужна возможность намеренно пропустить (не загружать) один из атрибутов, или возможность уже после загрузки при выполнении некоторых условий вернуть его в значение по умолчанию?
Кё>Такой задачи тоже не было.
Не было. Но если бы была, то делать это в одной процедуре было бы не очень удобно.
Кё> Если первое, то можно сделать аналогично (b[201].on_load), если второе, то наверное просто константу/define, которую использовать и в конструкторе и при возвращении значения по умолчанию:
E>>Или эта функция будет вызываться для всех атрибутов версии 201, которые имеют тип std::string?
Кё>...атрибутов с идентификатором 201... Вместо числа можно использовать их имя — "name", "x" и т.п., число компактней.
Теперь понял. Я думал, что речь идет о номере версии.
Вообще меня повело куда-то. Спасибо за ответы.
Я просто хотел сказать, что ручное формирование функции сериализации/десериализации при наличии опциональных атрибутов, проверки условий (как в примерах Шахтера) и наличием разных версий -- это тот еще геморрой. Который лучше переложить на плечи генератора кода.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Angler, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
Ш>>Как здесь обойтись одним общим методом (одно решение я знаю, но подсказывать не буду)?
A>Как более простой вариант — указывать в schema диапозон возможных значений
Это не прокатит, если условие более сложное, чем принадлежность диапазону.