Тип создаваемого объекта не привязан к типу запрошенного объекта. Если класс-потомок загружается через указатель на класс-предок, как в примере с CDocument , все равно создастся корректный класс-потомок. Это единственный способ корректно задать указатель vtbl так, чтобы он указывал на виртуальные функции объекта. Если объект в архиве не является "родственником" запрошенного объекта, MFC взведет исключение.
Этот механизм таит в себе две потенциальные ловушки для разработчиков. Во-первых, переименование сериализуемой структуры или класса сделает невозможным его восстановление из старых архивов. Во-вторых, MFC не записывает в архив длину каждого объекта. Поэтому, если MFC не сможет загрузить объект, она не сможет пропустить его и загрузить оставшуюся часть архива.
Оптимизация архивов
Когда я впервые просматривал этот код, мне виделись распухшие файлы с объектами и большие задержки при постоянном просмотре связанного списка. Но архивы MFC остаются маленькими, а скорость выполнения высокой благодаря хэш-идентификаторам.
MFC ведет хэш-таблицу всех классов и объектов, которые записываются в архив. При повторной записи объекта, MFC записывает вместо него идентификатор. Таким образом, при восстановлении информации из архива связанный список типов проходится только для новых классов. При загрузке объекта, экземпляры класса которого уже были прочитаны, нужная структура CRuntimeClass находится поиском в хэш-таблице.
Такое поведение также означает, что множественные ссылки на один и тот же объект обрабатываются корректно. Если объекты A и B при создании архива содержат указатели на один объект C, они оба будут указывать на один объект C после восстановления их из архива. MFC также корректно восстановит циклические меж-объектные ссылки.
В результате все работает гораздо быстрее, чем я ожидал. На 486/66, MFC смогла сохранить и восстановить архив размером более мегабайта с 10000 экземплярами CArray менее, чем за 2 секунды.
Есть одно важное ограничение – хэш-таблица не может содержать больше 32766 классов и объектов в контексте одного архива. Это число включает в себя только классы, унаследованные от CObject и сериализуемые оператором operator<<, и не включает фундаментальные типы, например, short и long , CString и CPoint . (за дополнительной информацией о конструировании архивов обратитесь к MFC Technical Note 2: Persistent Object Data Format ).
Версии схем сериализации
Одной из самых слабодокументированных особенностей 32-битной MFC 3.2 является поддержка версий схем сериализации (versionable schemas), когда MFC позволяет функции Serialize() обрабатывать разные версии одного класса вместо того, чтобы взводить исключение. Эта особенность очень важна для эволюционирующего проекта. И хотя я опишу, как реализовать поддержку версий, в Visual C++ 2.x этот механизм содержит ошибку и рушит программу при выполнении. Рекомендую написать в Microsoft, как сильно Вы ждете исправления этой ошибки [ можно еще воспользоваться компилятором поновее – прим.пер. ].
В MFC с каждой структурой, использующей DECLARE_SERIAL и IMPLEMENT_SERIAL, ассоциирован номер версии. Обычно этот номер установлен в 1, как показано в большинстве MFC-примеров; например, так – IMPLEMENT_SERIAL(CStroke, CObject, 1) .
У каждой структуры или класса есть свой номер версии, который может изменяться независимо от остальных. MFC автоматически записывает в архив номер версии после идентификатора класса. До версии 3.0 в MFC не было полной поддержки механизма версий сериализации, поэтому старые версии MFC взводили исключение, когда номер схемы объекта в файле не совпадал с текущим номером схемы. Это рушило поддержку множественных схем.
В MFC 3.0 и более поздних версиях, такое поведение сохранилось, но его можно изменить. Если объединить оператором OR третий параметр макроса IMPLEMENT_SERIAL с константой VERSIONABLE_SCHEMA, MFC позволит работать с версией схемы сериализации в Вашей функции Serialize() . Например, чтобы установить номер версии документа в 3, используйте выражение DECLARE_SERIAL(CScribDoc, CDocument, VERSIONABLE_SCHEMA|3) .
Чтобы использовать эту возможность, при загрузке данных из архива класс должен вызвать функцию GetObjectSchema() в своей функции Serialize() , как показано на листинге 3.
Листинг 3
class CSmallObject : public CObject {
DECLARE_SERIAL(CSmallObject);
DWORD m_value; // было unsigned short в версии 1
};
IMPLEMENT_SERIAL(CSmallObject, CObject, VERSIONABLE_SCHEMA | 2);
CSmallObject::Serialize(CArchive& ar) {
if (ar.IsStoring()) {
Читать дальше