Before I continue the series about data serialization in Qt, there is one important related topic than needs to be mentioned first: the ability to dynamically create an object based on a class name.
Let's assume that we want to serialize a list of shapes. The Shape is an abstract class and actual objects stored in the list have various derived classes: Rectangle, Circle, etc. During serialization, we can save the class name and the object data for each item. During deserialization, we need to be able to create an instance of an appropriate class. This is where an object factory becomes necessary. In languages like C# or Java, which support reflection, it is possible to instantiate a class given as a string with a few lines of code. But in C++ there is no such mechanism.
The simple solution is to create a single function with a large switch (or series of ifs) that creates the object of an appropriate type. It's not particularly elegant and it breaks the object oriented design, but in many cases it's acceptable. However, when you have lots of classes that are scattered through different parts of the application, it may become hard to manage. And if the application has external modules or dynamically loaded plug-ins, it becomes even more difficult.
A much more elegant solution is when the factory is abstract and it doesn't know anything about the objects it creates. Instead, classes that can be instantiated using the factory have to be registered using some type of internal map. This way, each module or plug-in can independently register its own set of classes.
Qt has two mechanism that can be useful for creating such factories. They may seem similar, but in fact there are major differences.
It is very easy to create an object factory which relies on QMetaObject. An example can be seen here. However, this solution has a few disadvantages:
However it's not difficult to create a custom factory for classes of any type, that doesn't have these limitations. An example of a custom factory that works for any class that inherits QObject can be seen below:
class ObjectFactory
{
public:
template<typename T>
static void registerClass()
{
constructors().insert( T::staticMetaObject.className(), &constructorHelper<T> );
}
static QObject* createObject( const QByteArray& className, QObject* parent = NULL )
{
Constructor constructor = constructors().value( className );
if ( constructor == NULL )
return NULL;
return (*constructor)( parent );
}
private:
typedef QObject* (*Constructor)( QObject* parent );
template<typename T>
static QObject* constructorHelper( QObject* parent )
{
return new T( parent );
}
static QHash<QByteArray, Constructor>& constructors()
{
static QHash<QByteArray, Constructor> instance;
return instance;
}
};With this approach, there is no need to declare the constructor with Q_INVOKABLE. Also, if no appropriate constructor is found, a compile-time error will be reported in the constructorHelper() method as soon as the class is registered. This code is very easy to use:
ObjectFactory::registerClass<Foo>();
// ...
QObject* foo = ObjectFactory::createObject( "Foo" );It is also easy to modify this code so that it works for custom abstract class hierarchies that do not inherit QObject. For example, instead of using the class name retrieved from the QMetaObject as a key, it can use a key of any type that is passed to the registerClass() method or retrieved automatically from a static class member. Also a different set of parameters can be passed to the constructor depending on the needs.