MFC – Множественное наследование и сериализация - Реализация Serialize
ОГЛАВЛЕНИЕ
Реализация Serialize
Сериализация CObject и ESerializable не конфликтуют: может иметься класс, унаследованный от CObject и ETypeSerilizable (многократно), но избегайте реализации DECLARE/IMPLEMENT_SERIAL и экземпляра ETypeSerializable, потому что нельзя – для одного и того же объекта – сохранять его иногда одним образом, а иногда другим: карты, следящие за идентичностью объектов, различаются (встроены в CArchive для CObject* и реализованы с помощью SArchiveMaps для ESerializable*), следовательно, есть риск сохранить объект дважды и при загрузке загрузить две отдельные идентичные копии.
Однако, помимо этого предупреждения, есть еще одна более серьезная проблема – несколько базовых классов.
Допустим, класс C унаследован от A и B, оба из них унаследованы от D и виртуально от E. Допустим, все эти классы также могут существовать как независимые классы и все являются сериализуемыми (все они унаследованы от их соответствующего класса ETypeSerializable<>).
На следующем рисунке показана иерархическая схема.
Есть два экземпляра D и лишь один экземпляр E (но на него ссылаются дважды). Это ведет к двум проблемам.
Первое: избегайте сериализации уже сериализованных компонентов
Допустим, C::Serialize реализуется путем вызова A::Serialize и B::Serialize.
Допустим, A::Serialize вызывает D::Serialize и E::Serialize. И B::Serialize вызывает D::Serilize и E::Serilize.
Сериализуйте C, и E сериализуется дважды. То есть , вероятно, потребуется проверить перед сериализацией чего-то, было ли это уже сериализовано. Но проблема не заключена в циклических указателях: на E не ссылаются A и B с помощью указателя.
Конечно, можно не вызывать E::Serialize из B, но что произойдет, если B существует сам по себе (не как базовый класс для C)? Его компонент E будет отсутствовать.
Для решения этой проблемы в SArchiveMaps была реализована карта, ключ которой формируется парой указателей: один – для SSerializWatchDoc, а второй – для ESerializable. Это значение - UINT, что на самом деле представляет собой счетчик.
SSerilizeMaps, который уже предоставляет функцию MapObject (она вызывается с помощью “Load” и “Save”, но можно вызывать ее в случаях, аналогично требующих вызова CArhive::MapObject для производного CObject), также предоставляет MapWatchDog, принимающий как аргументы SSerializeWatchDog и ESerializable.
SSerializeWatchDog, в свою очередь, предоставляет только одну функцию, а именно:
bool Locked(CArchive& ar, LPVOID pInstance).
Эта функция находит карты, связанные с архивом, и вызывает MapWatchDog, передавая себя заданный LPVOID. MapWatchDog ищет (и выделяет) запись и увеличивает счетчик. Locked возвращает true, если возвращенный счетчик больше 1.
Какую бы составляющую C, являющегося порождением от виртуального E, вы бы ни привели к E*, вы всегда будете получать одно и то же значение указателя (указатель "this" для E одинаков, но есть два разных указателя "this" для D)
Многократной сериализации базовых классов можно избежать путем создания статической переменной SSerializeWatchDog в начале функции Serilize и немедленного возврата управления, если Locked(ar, this) вернул true: это значит, что это тело (представленное переменной-сторожем) уже было выполнено над тем экземпляром (представленным this, приведенным к LPVOID)
Все структуры данных удаляются при уничтожении SArchiveMaps. Ниже приведены примеры сериализаций C, B и E
void C::Serialize(CArchive& ar)
{
static SSerializeWatchDog wd;
if(wd.Locked(ar, this)) return;
A::Serialize(ar);
B::Serialize(ar);
if(ar.IsStoring())
//сохранить члены C
else
//загрузить члены C
}
void B::Serialize(BArchive& ar)
{
static SSerializeWatchDog wd;
if(wd.Locked(ar, this)) return;
E::Serialize(ar);
D::Serialize(ar);
if(ar.IsStoring())
//сохранить члены B
else
//загрузить члены B
}
void E::Serialize(BArchive& ar)
{
static SSerializeWatchDog wd;
if(wd.Locked(ar, this)) return;
if(ar.IsStoring())
//сохранить члены E
else
//загрузить члены E
}
Макрос GE_SERIALIZE_CHECK_MULTIPLE() реализует первые строки кода функций сериализации.
Второе: различайте компонент одного и того же типа
Допустим, есть два D*, указывающих соответственно на два разных компонента D одного и того же объекта C (объект C на предыдущем рисунке). При сохранении первого указателя весь C сериализуется, тогда как при сохранении второго должна сохраняться только метка карты.
Но при обратной загрузке нельзя загрузить C и затем преобразовать в D*, потому что такое преобразование будет неоднозначным. Поэтому метку карты нельзя генерировать только для целых объектов (как делает MFC), а надо генерировать для каждого компонента.
Решение проблемы – отметить на карте обобщенные LPVOID (два D* различаются) и создать метку для каждого во время сериализации C. Это легко делается с помощью SArchiveMaps::MapWatchDog: эта функция вызывается в теле Serialize, передавая указатель “this”: для решения проблемы вызывается MapObject(pInstance).
Следовательно, чтобы решить проблему нескольких базовых классов, всегда в каждом теле Serialize определяйте static SSrializeWatchDog и вызывайте Locked. Он отметит объект на карте и проверит, было ли тело уже выполнено для этого экземпляра. Если он вернет истину, сразу возвращайте управление из этого тела. Если хотите, используйте макрос GE_SERIALIZE_CHECK_MULTIPLE(CArchive). Он сам сделает эти операции.