Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Serialization - Special Considerations

Boost , ,

C++ Boost

Serialization

Special Considerations


Object Tracking
Class Information
Helper Support
Archive Portability
Numerics
Traits
Binary Archives
XML Archives
Exporting Class Serialization
Static Libraries and Serialization
DLLS - Serialization and Runtime Linking
Plugins
Multi-Threading
Optimizations
Archive Exceptions
Exception Safety

Object Tracking

Depending on how the class is used and other factors, serialized objects may be tracked by memory address. This prevents the same object from being written to or read from an archive multiple times. These stored addresses can also be used to delete objects created during a loading process that has been interrupted by throwing of an exception.

Это может вызвать проблемы в прогамах, где копии разных объектов сохраняются с одного адреса.<


template<class Archive>
void save(boost::basic_oarchive  & ar, const unsigned int version) const
{
    for(int i = 0; i < 10; ++i){
        A x = a[i];
        ar << x;
    }
}
>В этом случае данные, подлежащие сохранению, находятся в стеке. Каждая итерация цикла обновляет значение в стеке. Таким образом, хотя данные изменяют каждую итерацию, адрес данных не изменяется. Если a [i] представляет собой массив объектов, отслеживаемых по адресу памяти, библиотека пропустит хранение объектов после первого, поскольку предполагается, что объекты по тому же адресу на самом деле являются одним и тем же объектом.

Чтобы помочь обнаружить такие случаи, операторы выходных архивов ожидают, что будут переданы<const>справочные аргументы.

Учитывая это, приведенный выше код вызовет утверждение компиляции времени. Очевидным исправлением в этом примере является использование<


template<class Archive>
void save(boost::basic_oarchive & ar, const unsigned int version) const
{
    for(int i = 0; i < 10; ++i){
        ar << a[i];
    }
}
>, которое будет компилироваться и работать без проблем. Использование<const>операторами выходных архивов гарантирует, что процесс сериализации не изменит состояние серийируемых объектов. Попытка сделать это представляла бы собой расширение понятия спасения государства с каким-то неочевидным побочным эффектом. Это почти наверняка будет ошибкой и вероятным источником очень тонких ошибок.

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

Аналогичная проблема может возникнуть, когда различные объекты загружаются по адресу, отличному от конечного местоположения:<


template<class Archive>
void load(boost::basic_oarchive  & ar, const unsigned int version) const
{
    for(int i = 0; i < 10; ++i){
        A x;
        ar >> x;
        std::m_set.insert(x);
    }
}
>В этом случае адрес<x>является тем, который отслеживается, а не адресом нового элемента, добавленного в набор. Оставшись без внимания, это нарушит функции, которые зависят от отслеживания, такие как загрузка объекта через указатель. В программу будут введены тонкие баги. Это может быть решено путем изменения вышеупомянутого кода таким образом:<

template<class Archive>
void load(boost::basic_iarchive  & ar, const unsigned int version) const
{
    for(int i = 0; i < 10; ++i){
        A x;
        ar >> x;
        std::pair<std::set::const_iterator, bool> result;
        result = std::m_set.insert(x);
        ar.reset_object_address(& (*result.first), &x);
    }
}
>Это позволит скорректировать информацию отслеживания таким образом, чтобы она отражала место последнего упокоения перемещаемой переменной, и тем самым устранить вышеупомянутую проблему.

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

По умолчанию не отслеживаются типы данных, обозначенные примитивными чертой сериализации классауровня реализации. Если желательно отслеживать общий примитивный объект через указатель (например,<long>, используемый в качестве эталонного счета), Он должен быть завернут в класс / структуру, чтобы он был идентифицируемым типом. Альтернатива изменения уровня реализации<long>повлияет на все<long>, сериализованные во всей программе, — вероятно, не на то, что кто-то намеревается.

Возможно, мы захотим отслеживать адреса, даже если объект никогда не сериализуется через указатель. Например, виртуальный базовый класс должен быть сохранен / загружен только один раз. Установив эту черту сериализации<track_always>, мы можем подавить избыточные операции сохранения / загрузки.<


BOOST_CLASS_TRACKING(my_virtual_base_class, boost::serialization::track_always)
>

Помощь

Некоторые типы, особенно те, которые имеют сложное поведение в течение жизни или ограниченный доступ к их внутреннему состоянию, могут нуждаться или извлекать выгоду из сложных алгоритмов сериализации. Основным мотивирующим аргументом является Shared_ptr. Поскольку экземпляры загружаются, они должны быть «сопоставлены» с любыми другими экземплярами, которые уже были загружены. Таким образом, таблица ранее загруженных экземпляров должна поддерживаться при загрузке архива, содержащего экземпляры shared_ptr. Без сохранения такой таблицы файл share_ptr будет серийным.

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


template
class shared_ptr
{
   ...
};
BOOST_SERIALIZATION_SPLIT_FREE(shared_ptr)
class shared_ptr_serialization_helper
{
  // table of previously loaded shared_ptr
  // lookup a shared_ptr from the object address
  shared_ptrlookup(const T *);
  // insert a new shared_ptr
  void insert>(const shared_ptr*);
};
namespace boost {
namespace serialization {
template<class Archive>
void save(Archive & ar, const shared_ptr & x, const unsigned int /* version */)
{
    // save shared ptr
    ...
}
template<class Archive>
void load(Archive & ar, shared_ptr & x, const unsigned int /* version */)
{
    // get a unique identifier.  Using a constant means that all shared pointers
    // are held in the same set.  Thus we detect handle multiple pointers to the
    // same value instances in the archive.
    const void * shared_ptr_helper_id = 0;
    shared_ptr_serialization_helper & hlp =
        ar.template get_helper<shared_ptr_serialization_helper>(helper_instance_id);
    // load shared pointer object
    ...
    shared_ptr_serialization_helper & hlp =
        ar.template get_helper<shared_ptr_serialization_helper>(shared_ptr_helper_id);
    // look up object in helper object
    T * shared_object hlp.lookup(...);
    // if found, return the one from the table
    // load the shared_ptr data
    shared_ptrsp = ...
    // and add it to the table
    hlp.insert(sp);
    // implement shared_ptr_serialization_helper load algorithm with the aid of hlp
}
} // namespace serialization
} // namespace boost
><get_helper<shared_ptr_serialization_helper>();>создает вспомогательный объект, связанный с архивом при первом вызове; последующие вызовы возвращают ссылку на объект, созданный в первую очередь, так что<hlp>может эффективно использоваться для хранения контекстной информации, сохраняющейся посредством сериализации различных<complex_type>объектов в одном и том же архиве.

Могут быть созданы помощники для сохранения и загрузки архивов. В одной и той же программе может быть несколько разных помощников или один и тот же помощник, созданный отдельно от разных частей программы. Это то, что делает помощь_instance_id необходимой. В принципе это может быть любое уникальное целое число. На практике проще всего использовать адрес содержащейся в нем функции сериализации. Приведенный выше пример использует эту технику.

Информация о классе

По умолчанию для каждого сериализованного класса информация о классе записывается в архив. Эта информация включает в себя номер версии, уровень реализации и поведение отслеживания. Это необходимо для того, чтобы архив можно было правильно десериализировать, даже если последующая версия программы изменяет некоторые текущие значения признаков для класса. Пространство для этих данных минимально. Накладные расходы на время выполнения немного, так как каждый класс должен быть проверен, чтобы увидеть, есть ли у него уже включенная в архив информация о классе. В некоторых случаях даже это можно считать слишком большим. Эти дополнительные накладные расходы могут быть устранены путем установленияуровня реализацииклассовой черты:<boost::serialization::object_serializable>

.Отключение отслеживания и сериализации информации о классе приведет к чистому встроенному коду шаблона, который в принципе может быть оптимизирован до простой записи / чтения потока.Устранение всех накладных расходов на сериализацию таким образом обходится дорого. После того, как архивы будут выпущены для пользователей, признаки сериализации класса не могут быть изменены без признания старых архивов недействительными. Включение информации о классе в архив гарантирует, что она будет читаемой в будущем, даже если определение класса будет пересмотрено. Легковесная структура, такая как пиксель дисплея, может быть объявлена в заголовке, таком как:<


#include <boost/serialization/serialization.hpp>
#include <boost/serialization/level.hpp>
#include <boost/serialization/tracking.hpp>
// a pixel is a light weight struct which is used in great numbers.
struct pixel
{
    unsigned char red, green, blue;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int /* version */){
        ar << red << green << blue;
    }
};
// elminate serialization overhead at the cost of
// never being able to increase the version.
BOOST_CLASS_IMPLEMENTATION(pixel, boost::serialization::object_serializable);
// eliminate object tracking (even if serialized through a pointer)
// at the risk of a programming error creating duplicate objects.
BOOST_CLASS_TRACKING(pixel, boost::serialization::track_never)
>

Переносимость архива

Несколько классов архивов создают свои данные в виде текста или переносного двоичного формата. Должна быть возможность сохранить такой класс на одной платформе и загрузить его на другую. Это зависит от нескольких условий.

Числа

Архитектура машинного считывания архива должна содержать сохраненные данные. Например, компилятор gcc резервирует 4 байта для хранения переменной типа<wchar_t>, в то время как другие компиляторы резервируют только 2 байта. Таким образом, возможно, что может быть записано значение, которое не может быть представлено программой загрузки. Это довольно очевидная ситуация и легко обрабатывается с помощью численных типов в

Специальный интегральный тип<std::size_t>, который является типдефом интегральных типов, гарантированно достаточно большим, чтобы удерживать размер любой коллекции, но его фактический размер может отличаться в зависимости от платформы. Обертка<collection_size_type>существует для обеспечения возможности переносной сериализации размеров коллекций архивом. Рекомендуемые варианты для портативной сериализации размеров коллекции должны использовать либо 64-битное, либо целочисленное представление переменной длины.

Черты

Другая потенциальная проблема иллюстрируется следующим примером:<

template<class T>
struct my_wrapper {
    template<class Archive>
    Archive & serialize ...
};
...
class my_class {
    wchar_t a;
    short unsigned b;
    template<class Archive>
    Archive & serialize(Archive & ar, unsigned int version){
        ar & my_wrapper(a);
        ar & my_wrapper(b);
    }
};
>Если<my_wrapper>использует черты сериализации по умолчанию, может возникнуть проблема. С признаками по умолчанию каждый раз, когда в архив добавляется новый тип, добавляется бухгалтерская информация. Таким образом, в этом примере архив будет включать такую бухгалтерскую информацию для<my_wrapper<wchar_t>>и для<my_wrapper<short_unsigned>>. Или нет? А как насчет компиляторов, которые рассматривают<wchar_t>как синоним<unsigned short>? При этом существует только один отдельный тип — не два. Если архивы передаются между программами с компиляторами, которые отличаются по своей обработке<wchar_t>, то операция загрузки выйдет из строя катастрофическим образом.

Одним из средств для этого является присвоение шаблону<my_template>признаков сериализации, так что информация о классе для инстанциаций этого шаблона никогда не сериализуется. Этот процесс описанвышеи используется дляпар именных ценностей. Обертывателям обычно присваиваются такие черты.

Еще один способ избежать этой проблемы — присвоить всем специализациям шаблона<my_wrapper>черты сериализации для всех примитивных типов, чтобы информация о классе никогда не сохранялась. Это то, что было сделано для реализации сериализации коллекций STL.

Бинарные архивы

Стандартный поток i/o на некоторых системах расширит символы линейного питания до обратной передачи/линейного питания на выходе. Это создает проблему для бинарных архивов. Самый простой способ справиться с этим — открыть потоки для бинарных архивов в «бинарном режиме» с помощью флага<ios::binary>. Если этого не сделать, сгенерированный архив будет нечитаемым.

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

XML Архивы

XML архивы представляют собой несколько особый случай. XML-формат имеет вложенную структуру, которая хорошо отображает шаблон «рекурсивный посетитель класса», используемый системой сериализации. Однако XML отличается от других форматов тем, что для каждого элемента данных требуется имя. Наша цель состоит в том, чтобы добавить эту информацию в спецификацию сериализации класса, при этом позволяя использовать код сериализации с любым архивом. Это достигается требованием, чтобы все данные, сериализованные в XML-архив, были сериализованы какпара имени-значения. Первый элемент - это имя, которое будет использоваться в качестве XML-метки для элемента данных, а второй - ссылка на сам элемент данных. Любая попытка сериализовать данные, не завернутые вименную пару, будет заблокирована во время компиляции. Система реализована таким образом, что для других классов архивов сериализуется только ценностная часть данных. Часть имени отбрасывается во время компиляции. Таким образом, всегда используяпары значений имени, можно гарантировать, что все данные могут быть сериализованы для всех классов архивов с максимальной эффективностью.

Сериализация экспортного класса

В другом местев этом руководстве мы описали<BOOST_CLASS_EXPORT>. Экспорт подразумевает две вещи:
  • Обосновывает код, который не упоминается иначе.
  • Связывает внешний идентификатор с классом, подлежащим сериализации. Тот факт, что класс явно не упоминается, подразумевает это требование.
В C++ использование кода, не упомянутого явно, реализуется с помощью виртуальных функций. Следовательно, необходимость экспорта подразумевается использованием производного класса, которым манипулируют с помощью указателя или ссылки на его базовый класс.

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

Обратите внимание, что для имплементации этой функциональности требуется, чтобы макрос<BOOST_CLASS_EXPORT>появилсяпослевключения любых заголовков классов архивов, для которых код должен быть инстанцирован. Таким образом, код, который использует<BOOST_CLASS_EXPORT>, будет выглядеть следующим образом:<


#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
... // other archives
#include "a.hpp" // header declaration for class a
BOOST_CLASS_EXPORT(a)
... // other class headers and exports
>Это будет справедливо независимо от того, является ли код частью отдельного исполняемого файла, статической библиотеки или динамической или общей библиотеки.

Включение<BOOST_CLASS_EXPORT>в сам заголовок a.hpp, как это было бы с другими чертами сериализации, затруднит или сделает невозможным соблюдение вышеупомянутого правила относительно включения заголовков архива до того, как будет использован<BOOST_CLASS_EXPORT>. Для этого лучше всего использовать<BOOST_CLASS_EXPORT_KEY>в объявлениях заголовка и<BOOST_CLASS_EXPORT_IMPLEMENT>в файле определения класса.

Эта система имеет определенные последствия для размещения кода в статических или общих библиотеках. Размещение<BOOST_CLASS_EXPORT>в библиотечном коде не будет иметь никакого эффекта, если не включены заголовки классов архивов. Поэтому при создании библиотеки следует включать все заголовки для всех классов архивов, которые он ожидает использовать. В качестве альтернативы можно включить заголовки только дляПолимопрхических архивов.

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

Обратите внимание, что реализация этой функциональности зависит от конкретных расширений поставщика языка C++. Таким образом, нет гарантированной переносимости программ, которые используют этот объект. Тем не менее, все компиляторы C++, которые тестируются с ускорением, обеспечивают необходимые расширения. Библиотека содержит дополнительные декларации, требуемые каждым из этих составителей. Разумно ожидать, что будущие компиляторы C++ будут поддерживать эти расширения.

Статические библиотеки и сериализация

Код для сериализации типов данных может быть сохранен в библиотеках так же, как и для остальной части реализации типа. Это хорошо работает и может сэкономить огромное количество времени на компиляцию.
  • Только компилируйте определения сериализации в библиотеке.
  • Явно инстанцируйте код сериализации для ВСЕХ классов архивов, которые вы собираетесь использовать в библиотеке.
  • Для экспортируемых типов используйте только<BOOST_CLASS_EXPORT_KEY>в заголовках.
  • Для экспортируемых типов используют только<BOOST_CLASS_EXPORT_IMPLEMENT>в определениях, составленных в библиотеке. Для любого конкретного типа должен быть только один файл, который содержит<BOOST_CLASS_EXPORT_IMPLEMENT>для этого типа. Это гарантирует, что в программе будет существовать только одна копия кода сериализации. Это позволяет избежать потери пространства и возможности иметь разные версии кода сериализации в одной программе. Включение<BOOST_CLASS_EXPORT_IMPLEMENT>в несколько файлов может привести к отказу ссылки из-за дублированных символов или к исключению времени выполнения.
  • Код для сериализации должен быть только в библиотеке,
  • Ознакомьтесь с идиомойPIMPL.
Это проиллюстрировано<demo_pimpl.cpp>,<demo_pimpl_A.cpp>и<demo_pimpl_A.hpp>, где реализация сериализатона находится в статической библиотеке, полностью отдельной от основной программы.

DLLS - Сериализация и соединение времени выполнения

Код сериализации может быть размещен в библиотеках для соединения во время выполнения. То есть код может быть размещен в DLLS (Windows) Shared Libraries (*nix) или в статических библиотеках, а также в главном исполняемом файле. Лучшая методика та же, что описана выше для библиотек. Тестовый пакет библиотеки сериализации включает в себя следующие программы для иллюстрации того, как это работает:

<test_dll_simple>и<dll_a.cpp>, где реализация последовательностей также полностью отделена от основной программы, но код загружается во время выполнения. В этом примере этот код загружается автоматически при запуске программы, которая его использует, но он также может быть загружен и выгружен с помощью вызова API, зависящего от ОС.

Также включены<test_dll_exported.cpp>и<polymorphic_derived2.cpp>, которые аналогичны вышеперечисленным, но включают испытания экспортных и неторти-объектов в контексте DLLS.

Для достижения наилучших результатов напишите свой код в соответствии со следующими рекомендациями:

  • Не включайте код<inline>в классы, используемые в DLLS. Это будет генерировать дублированный код в DLLS и магистрали. Это просто дублирует код. Хуже того, это позволяет одновременно существовать различным версиям одного и того же кода. Этот тип ошибки оказывается мучительно трудно отладить. Наконец, это открывает возможность того, что упомянутый модуль может быть эксплицитно разгружен, что приведет (надеюсь) к ошибке времени выполнения. Это еще один баг, который не всегда воспроизводим или легко найти. Для шаблонов членов класса используют что-то вроде<
    
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version);
    
    >в заголовке и<
    
    template<class Archive>
    void myclass::serialize(Archive & ar, const unsigned int version){
    	...
    }
    BOOST_EXPORT_CLASS_IMPLEMENT(my_class)
    #include <boost/archive/text_oarchive>
    #include <boost/archive/text_iarchive<
    template myclass::serialize(boost::archive::text_oarchive & ar, const unsigned int version);
    template myclass::serialize(boost::archive::text_iarchive & ar, const unsigned int version);
    ... // repeat for each archive class to be used.
    
    >в файле реализации. Это приведет к генерации всего кода, необходимого только в одном месте. Библиотека не обнаруживает такого рода ошибки.
  • Если DLLS должны загружаться и выгружаться явно (например, используя<dlopen>в *nix или<LoadLibrary>в Windows). Постарайтесь устроить так, чтобы они разгружались в обратной последовательности. Это должно гарантировать, что проблемы будут устранены, даже если вышеприведенное руководство не было выполнено.

Плагины

Для реализации библиотеки требовались различные средства манипулирования типами во время выполнения. Это<extended_type_info>для связывания классов с внешними идентифицирующими струнамиGUIDи<void_cast>для литья между указателями родственных типов. Для завершения функциональности<extended_type_info>добавлена возможность конструирования и уничтожения соответствующих типов. Чтобы использовать эту функциональность, необходимо указать, как создается каждый тип. Это должно быть сделано в то время, когда класс экспортируется. Таким образом, более полным примером кода выше будет:<

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
... // other archives
#include "a.hpp" // header declaration for class a
// this class has a default constructor
BOOST_SERIALIZATION_FACTORY_0(a)
// as well as one that takes one integer argument
BOOST_SERIALIZATION_FACTORY_1(a, int)
// specify the GUID for this class
BOOST_CLASS_EXPORT(a)
... // other class headers and exports
>При этом можно построить, сериализовать и уничтожить класс, о котором известно толькоГИДи базовый класс.

Многопоточность

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

Однако одновременное написание/прочтение различных архивов в различных задачах допускается, поскольку каждый экземпляр архива (почти) полностью независим от любого другого экземпляра архива. Единственной совместно используемой информацией являются таблицы некоторых типов, которые были реализованы с использованием безблокировочной защиты потока.<singleton>, описанные в других документах.

Эта однотонная реализация гарантирует, что вся эта общая информация инициализируется при загрузке модуля кода, который содержит ее. Библиотека сериализации заботится о том, чтобы эти структуры данных не были впоследствии изменены. Единственная проблема может возникнуть, если код загружается / выгружается, в то время как другая задача - сериализация данных. Это может произойти только для типов, сериализация которых реализована в динамически загруженной/незагруженной DLL или общей библиотеке. Если же этого избежать, то

  • Доступ к одному и тому же экземпляру архива из разных задач.
  • Загрузка/выгрузка DLLS или общих библиотек, в то время как любые экземпляры архива открыты.
Библиотека должна быть в безопасности.

Оптимизация

В критических приложениях производительности, которые сериализуют большие наборы смежных данных однородных типов, нужно избегать накладных расходов на сериализацию каждого элемента по отдельности, что является мотивацией для.<array>Обертка. Функции сериализации для типов данных, содержащих смежные массивы однородных типов, таких как<std::vector>,<std::valarray>или<boost::multiarray>, должны сериализовать их с использованием.<array>обертка для использования этих оптимизаций. Архивные типы, которые могут обеспечить оптимизированную сериализацию для смежных массивов однородных типов, должны реализовывать их, перегружая сериализацию обертки<array>, как это делается для бинарных архивов.

Архивные исключения

Безопасность исключения


& копия; Авторское правоРоберт Рэми2002-2004. Распространяется под лицензией Boost Software License, версия 1.0. (См. сопроводительный файл LICENSE_1_0.txt или копию по адресу http://www.boost.org/LICENSE_1_0.txt)

Статья Serialization - Special Considerations раздела может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-20 15:11:40/0.012040853500366/0