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>.