Ещё одна сериализация для C++

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

Хотя инструментов для сериализации существует достаточно много, я предлагаю вашему вниманию ещё один. Он не лучше и не хуже других, и был создан с акцентом на простоту (кто бы мог подумать?) и компактность (опять же!), не сильно влияющую на производительность работы с ранее сериализованными данными.


Привет! Я Андрей Коваленко (Keva). Я занимаюсь лингвистическими задачами и делаю поисковые движки. Первый Апорт!, движок Rambler образца 2000 года и украинская <META> - мои разработки. Недавно завершил работу над большой корпоративной поисковой системой МойОфис.

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

Инструмент исполнен как набор шаблонных методов, поддерживающих наиболее распространённые типы и классы данных, и может дополняться в коде проекта короткими определениями для отправки результатов в тот или иной коллектор. Всего декларируются 4 шаблонных метода в глобальном пространстве имён, где T - сериализуемый тип, O - коллектор, а s - источник:

template <class T> size_t  GetBufLen( const T& );
template <class O,
          class T> O*      Serialize( O*, const T& );
template <class S,
          class T> S*      FetchFrom( S*, T& );
template <class S,
          class T> S*      SkipToEnd( S*, const T* );

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

Serialize( O* o, const T& t ) записывает объект t в коллектор, типизированный указатель на который передаётся первым параметром.

FetchFrom( S* s, T& t ) извлекает из источника s объект t.

SkipToEnd( S* s, const T* ) проматывает объект заданного типа T без его создания, переходя к следующему сериализованному объекту. В данном случае T* используется лишь для указания типа, и обычное его использование - передача (const T*)nullptr.

Для накопителей "память (char*)" и "c-style файл (FILE*)" примитивы заданы сразу:

template <> auto  Serialize( char* o, const void* p, size_t l ) -> char*
  {  return o != nullptr ? l + (char*)memcpy( o, p, l ) : nullptr;  }
template <> auto  Serialize( FILE* o, const void* p, size_t l ) -> FILE*
  {  return o != nullptr && fwrite( p, sizeof(char), l, o ) == l ? o : nullptr;  }

template <> auto  FetchFrom( const char* s, void* p, size_t l ) -> const char*
  {  return s != nullptr ? (memcpy( p, s, l ), l + s) : nullptr;  }
template <> auto  FetchFrom( FILE* s, void* p, size_t l ) -> FILE*
  {  return s != nullptr && fread( p, sizeof(char), l, s ) == l ? s : nullptr;  }

template <> auto  SkipBytes( const char* s, size_t l ) -> const char*
  {  return s != nullptr ? s + l : s;  }
template <> auto  SkipBytes( FILE* s, size_t l ) -> FILE*
  {  return s != nullptr && fseek( s, l, SEEK_CUR ) == 0 ? s : nullptr;  }

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

class blackhole {};

template <> auto  Serialize( blackhole* o, const void*, size_t ) -> blackhole*
  {  return o;  }

Библиотека содержит примитивы кросплатформенной записи и чтения базовых типов (за исключением float и double - они пишутся as is), а также многих классов из стандартной библиотеки - vector, string, map, list, pair, tuple. Для собственных классов достаточно создать одноимённые методы, и они также будут успешно обрабатываться:

class storable
{
  int i;
  std::string s;
public:
  template <class O> O* Serialize( O* o ) const
  {
    return ::Serialize( ::Serialize( o, i ), s );
  }
  template <class S> S* FetchFrom( S* s )
  {
    return ::FetchFrom( ::FetchFrom( s, i ), this->s );
  }
};

...

storable object;

::Serialize( stdout, object );

Целочисленные значения от двух байт (uint16_t и выше) сериализуются с базовой компрессией - порциями по 7 бит для ненулевой части бит. То есть uint32_t(127) займёт 1 байт, а 128 уже два, а вот 65535 (16 значимых бит) уже 3 байта. На круг получается компактнее.

Типизация в коллекторах не предусмотрена - это ответственность самой программы - знать, что читать. Поэтому при желании можно сериализовать std::vector<int>, а десериализовать std::list<int>; сериализовать const char[] = "...", а десериализовать std::string.

Про сериализацию данных произвольной структуры, в том числе и потенциально неполных, на днях напишу отдельно.

Ну а код доступен на github.

Источник: https://habr.com/ru/articles/888174/


Интересные статьи

Интересные статьи

Сегодня мы поговорим о том, как highload-блоки в Битрикс24 могут помочь справиться с большими объемами данных и обеспечить стабильную работу систем при высоких нагрузках. Highload-блоки в Битрикс24 — ...
И опять о проверялках. Сколько уже на эту тему было опровержений – от SEOшников всех мастей, от самого Мэтта Каттса из Гугла, но дело проверялок продолжает жить в самой своей уродливой форме. И для эт...
Читая с утра новостную ленту – я обнаружил в ней очередное апокалипсическое предсказание. На этот раз от Элиезера Юдковского – известного рационального блогера, сооснователя Института Исследования Иск...
Есть несколько способов добавить водяной знак в Битрикс. Рассмотрим два способа.
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...