MFC – Множественное наследование и сериализация - Предлагаемое решение
ОГЛАВЛЕНИЕ
Предлагаемое решение
Находится в “Factory.h” и “Factory.cpp”. Набор шаблонных классов на базе RTTI для управления сериализацией вместо макросов.
Что такое SFactory?
Это замена CRuntimeClass. Это структура, содержащая const type_info* (берется из <typeinfo.h>, RTTI должен быть включен) и используемая для предоставления имени типа в виде строки.
SFactory привязывается к внутреннему std::list при создании и уничтожении и находится с помощью строки имени типа с использованием статической функции FindFactory. Он также определяет абстрактную функцию-член NewObject, но не реализует ее.
Полная функциональность реализована в шаблонном классе, унаследованном от SFactory, STypeFactory<E>. Его конструктор инициализирует type_info базового класса, чтобы он указывал на typeid(E), и NewObject переопределяется, чтобы он возвращал new E.
Обычно никогда не работают напрямую с этими классами. Между тем с ними взаимодействуют через другие классы: ESerializable и ETypeSerializable<E>.
ESerializable – класс, являющийся виртуально наследуемым от любого класса, который надо сделать сериализуемым (виртуальный, потому что ваш класс может иметь несколько базовых классов, каждый из которых сам может быть сериализуемым: но нужен только один экземпляр ESerializable на каждый объект).
Он определяет абстрактные функции: virtual void Serialize(CArchive& ar) и виртуальная SFactory* GetFactory(), по умолчанию возвращающая значение переменной члена.
Обычно об этом классе не заботятся. Заботятся о производном от него шаблоне: ETypeSerializable<E>.
Он виртуально наследует ESerializable и связывает STypeFactory<E> с типом E, устанавливая переменную члена его базового класса. GetFactory возвращает STypeFactory<E>.
Сейчас:
• Чтобы сделать класс сериализуемым, надо унаследовать этот класс от ETypeSerilizable<yourclass> как последнего базового класса: это заставляет GetFactory() возвращать правильное значение (если это класс MFC, пусть первым базовым классом будет базовый класс MFC). Это равнозначно DECLARE_SERIAL
• Чтобы сделать класс загружаемым, надо объявить на глобальном уровне (внутри безымянного пространства имен) переменную типа ETypeSerilizable<yourclass>. Ведь чтобы механизм загрузки работал, должна существовать хотя бы одна фабрика для вашего типа. Даже если еще не было загружено или создано ни одного объекта yourclass. Это равнозначно IMPLEMENT_SERIAL
Пример ниже:
// В файле заголовка (.H)
class YourClass: public YourBase, public ETypeSerializable<YourClass>
//Замечание: должен
// быть
// последний базовый класс
{
// члены вашего класса
};
// в файле исходного кода (.CPP)
namespace {
ETypeSerializable<YourClass> g_yourclassfactory;
}
Если надо сериализовать указатель – вызывается SavePtr (при сохранении) и LoadPtr (при загрузке) с передачей переменной-указателя.
Замечание: Не были определены такие операторы, как << и >>, потому что они конфликтуют с оператором сохранения и загрузки указателя MFC.
Так как используются шаблоны, а не макросы, не будет никаких проблем с составными именами, такими как имена в пространствах имен или шаблонные классы.
Однако, чтобы сделать шаблонные классы сериализуемыми, нужен ETypeSerilizable<yourtemplateclass<yourparameters> > для каждого создания экземпляра шаблона, используемого в программе.
Например, если заголовок таков
template<class T>
class YourTemplate:
public ETypeSerializable<YourTemplate<T> >
{
// члены
// функции (включая Serialize)
};
и вы собираетесь использовать YourTemplate для UINT и CString, то надо в файле cpp на глобальном уровне создать следующее
namespace { //безымянное пространство имен
ETypeSerializable<YourTemplate<UINT> > g_yourtemplateForUint;
ETypeSerializable<YourTemplate<CString> > g_yourtemplateForCString;
}
Карты сериализации
При сохранении указателей, чтобы избежать многократной сериализации одного и того же объекта и отслеживания зацикливания, пришлось связать с архивом сериализации некоторые карты, связав метку с объектом (и наоборот).
Именно это (более или менее) делает MFC в своей реализации CArchive, но эти карты предназначены для производного CObject, а не для виртуально унаследованного ESerializable.
SArchiveMaps реализует то, что требуется. Должен быть создан его экземпляр, связанный с CArchive, перед началом сериализации/десериализации элементов ESerializable и уничтожен после завершения.
Более простой способ сделать это в приложении MFC – внутри производной реализации CDocument::Serialize: документы, фреймы и виды всегда создаются MFC через CDocTemplate и CRuntimeClass. При сохранении и загрузке CDocument::Serialize всегда вызывается. Итак ...
void CMyDocument::Serialize(CArchive& ar)
{
GE_::Safe::SArchiveMaps maps(ar); //будет существовать до возврата
if(ar.IsStoring())
{
ar << _valueobject;
ar << _pCobjectDerived;
GE_::Safe::SavePtr(ar, _pESerializableDerived);
}
else
{
ar >> _valueobject;
ar >> _pCobjectDerived;
GE_::Safe::LoadPtr(ar, _pESerializableDerived);
}
}
• _valueobject является членом, на который всегда ссылается значение (например, встроенные типы, или структура или классы, для которых определены копирование и присвоение, и оператор >> и << определены как CArchive& oprator<<(CArchive& ar, const SValueClass& value) и CArchive& operator>>(CArchive& ar, SValueClass& value).
• _pCObjectDerived является указателем на CObject или на производный класс CObject, для которого используются DECLARE_SERIAL и IMPLEMENT_SERIAL
• _pESerializableDerived является указателем на класс, порождающийся виртуально от ESerializable из-за порождения от ETypeSerializabe<yourclass>, для которого также определяется глобальная переменная ETypeSerializable<yourclass>.