Повысьте сериализацию по разыменованному указателю базового класса

У меня небольшая проблема с ускоренной сериализацией. Есть много примеров, которые показывают, как сериализовать указатель производного класса через указатель базового класса, просто используя BOOST_CLASS_EXPORT и BOOST_CLASS_EXPORT_IMPLEMENT. Это работает нормально и не имеет никаких проблем.

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

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

Рабочий пример:

Class A;
Class B : public A;

A* baseClass = new B();
ar << baseClass // works perfectly

Не рабочий пример:

Class A;
Class B : public A;
A* baseClass = new B();
ar << *baseClass; // only A is serialized

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

B* derivedClass = new B();
ar << *derivedClass; // works fine

Но все ссылки, которые у меня есть в моих структурах, относятся к типу базового класса. Также я не могу сериализовать указатель, так как мне не нужно создавать новые объекты при десериализации, а только «перезаписывать» содержимое поверх существующего экземпляра.

Я попытался сериализовать указатель и попытаться десериализовать существующий экземпляр, но это работает неправильно. Когда я говорю десериализовать существующий экземпляр, я имею в виду:

A* baseClass = new B();
// baseClass is used in the program and in a given moment, its contents must be overwrite, so:
ar >> *baseClass;

Как я уже сказал, мне не нужен новый экземпляр baseClass при десериализации. Итак, есть ли способ заставить это работать?


person Alvaro Luis Bustamante    schedule 15.07.2013    source источник
comment
Привет, немного опоздал, но я только что нашел этот вопрос. Я столкнулся с той же проблемой... Я думал, что проблема может быть решена с помощью ссылок, но сериализация через ссылку на базовый класс у меня не работает. Не могли бы вы решить эту проблему?   -  person TomasG    schedule 05.02.2014
comment
Наконец-то я использовал решение, указанное @TomasG прямо сейчас. В основном состоит в добавлении сериализации и десериализации виртуальных функций в классы, которые я хочу сериализовать таким образом. К счастью, их немного.   -  person Alvaro Luis Bustamante    schedule 05.02.2014
comment
спасибо за ответ, жаль, что нет более элегантного решения.   -  person TomasG    schedule 05.02.2014


Ответы (2)


Я столкнулся с той же проблемой, что и вы! Поэтому я просмотрел документ boost, который дает способ решить проблему, я могу определить класс D для управления производными объектами и использовать ar.register_type для сравнения класса abc, точно так же, как это :

 class base {
    ...
};
class derived_one : public base {
    ...
};
class derived_two : public base {
    ...
};
main(){
    ...
    base *b;
    ...
    ar & b; 
}

При сохранении b какой объект должен быть сохранен? При загрузке b какой объект должен быть создан? Должен ли это быть объект класса производный_один, производный_два или, может быть, базовый?

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

Если базовый класс является полиморфным, будет сериализован объект наиболее производного типа (в данном случае производный_один или производный_два). Вопрос о том, какой тип объекта должен быть сериализован, (почти) автоматически обрабатывается библиотекой.

Система «регистрирует» каждый класс в архиве при первой сериализации объекта этого класса и присваивает ему порядковый номер. В следующий раз, когда объект этого класса сериализуется в том же архиве, этот номер записывается в архив. Таким образом, каждый класс однозначно идентифицируется в архиве. Когда архив считывается обратно, каждый новый порядковый номер повторно связывается с читаемым классом. Обратите внимание, что это означает, что «регистрация» должна происходить как во время сохранения, так и во время загрузки, чтобы таблица целочисленного класса, построенная при загрузке, была идентична таблице целочисленного класса, построенной при сохранении. Фактически, ключ ко всей системе сериализации заключается в том, что вещи всегда сохраняются и загружаются в одной и той же последовательности. Это включает в себя «регистрацию».

main(){
    derived_one d1;
    derived_two d2:
    ...
    ar & d1;
    ar & d2;
    // A side effect of serialization of objects d1 and d2 is that
    // the classes derived_one and derived_two become known to the archive.
    // So subsequent serialization of those classes by base pointer works
    // without any special considerations.
    base *b;
    ...
    ar & b; 
}

Когда b читается, ему предшествует уникальный (для архива) идентификатор класса, который ранее был связан с классом производный_один или производный_два.

Если производный класс НЕ был автоматически "зарегистрирован", как описано выше, при вызове сериализации будет выдано исключение unregistered_class.

Это можно решить, явно зарегистрировав производный класс. Все архивы являются производными от базового класса, который реализует следующий шаблон:

template<class T>
register_type();

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

main(){
    ...
    ar.template register_type<derived_one>();
    ar.template register_type<derived_two>();
    base *b;
    ...
    ar & b; 
}

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

вы также можете использовать:

#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")

main(){
    ...
    base *b;
    ...
    ar & b; 

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

Может быть полезно для вас!! подробности вы можете увидеть здесь:http://www.boost.org/doc/libs/1_54_0/libs/serialization/doc/index.html

person minicaptain    schedule 15.07.2013
comment
Спасибо за ваш ответ, но это не решило мою проблему. ar.register_type равен BOOST_CLASS_EXPORT, как я уже упоминал в своем вопросе. Это работает при сериализации по указателю базового класса, но не при сериализации по разыменованному указателю базового класса. В примере bus_route все производные классы сериализуются по указателям базового класса (stops — это список указателей bus_stop). - person Alvaro Luis Bustamante; 15.07.2013

Я думаю, что понимаю проблему. Когда вы делаете

ar >> *DerivedClass;

вы передаете ссылку на operator<<. Теперь объекты, доступ к которым осуществляется через ссылки на базовый класс, не сериализуются должным образом, как я понимаю из ответа Роберта Рэми на этот вопрос в списке рассылки Boost-users. Хотя ответу уже несколько лет, я думаю, что он все еще остается верным, потому что, если подумать, serialize методы, которые он пишет, не являются виртуальными (это шаблоны, поэтому они не может быть виртуальным).

Таким образом, библиотека должна делать специальные вещи для обработки указателей, но не со ссылками. Уродливое решение, которое я нашел, состоит в том, чтобы добавить пару (виртуальных) функций сериализации, например:

virtual myser(iarchive &ia) {ia >> *this;}
virtual myser(oarchive &oa) {oa << *this;}

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

person TomasG    schedule 04.02.2014
comment
просто подход, который я использовал, наконец... поскольку для этого нет чистого решения. - person Alvaro Luis Bustamante; 05.02.2014