Prehistory
Actually for what this might be needed? After all, C ++ already provides quite flexible options for serializing to a stream. However, my task was to maximally universalize the process of serialization / deserialization for repeated use in projects.
So, it was necessary to organize the most flexible (de) serialization system in Qt so that
- either inheriting from the base class and expanding it
- either having a separate serializer class
be able to send a stream of data from the object in one command.
At the same time, in some way it should be possible to specify which data in the object should be serialized, and which can (and should) be “skipped”. Similarly, the ability to correctly set the data and the associated dependent values ​​inside the object should have been performed during deserialization.
Decision
')
The solution was found as a property system. Qt has add-ons over C ++ to provide a property system. We have the right to use properties only in classes inherited from QObject, having a Q_OBJECT tag. The assignment of properties occurs when using a macro of this type when describing a class in a header (example):
Q_PROPERTY(int Test READ readTest WRITE setTest)
As you can see from the example, you can assign a function to read (readTest) and write (setTest). The latter ensures the correctness of the installation during deserialization.
The parameters necessary for serialization are typed in the object as properties, thereby setting the list of properties necessary for ensuring the integrity of class data.
To provide access to properties in general (given that the base class about the class that transferred itself for serialization to know nothing and cannot) is obtained through the MetaObject system (describing an object with its property system and generated for any object) MetaProperty, which provide a description and access to properties. The result is the following code for (de) serialization:
bool SerializedBase::Serialize(QDataStream *dataStream)
{
if (dataStream == NULL)
return false ;
for ( int i = 1; i < this ->metaObject()->propertyCount(); i++)
{
QMetaProperty prop = this ->metaObject()->property(i);
const char * propName = prop.name();
*dataStream << ( this ->property(propName));
}
return true ;
}
bool SerializedBase::DeSerialize(QDataStream *dataStream)
{
if (dataStream == NULL)
return false ;
for ( int i = 1; i < this ->metaObject()->propertyCount(); i++)
{
QMetaProperty prop = this ->metaObject()->property(i);
const char * propName = prop.name();
QVariant v;
*dataStream >> v;
this ->setProperty(propName, v);
}
return true ;
}
* This source code was highlighted with Source Code Highlighter .
The result is a base class, inheriting from which we can serialize / deserialize an object into 1 command (if there is any data stream created).
The code for a free-standing class should be almost identical, except that add. checks in order to understand the object in front of us or the usual data structure).
Additional information and modifications
Objects in the form of properties and their serialization
The Q_DISABLE_COPY directive in the QObject class code does not give the ability to assign directly object-values ​​to properties. In order to provide the necessary flexibility, you need to perform 3 steps:
- Create a copy constructor. Suppose if an object is class AAA, then the constructor should look like this (in the header):
AAA(AAA* copy)
. It actually defines the actions when copying an object. - Register the metaclass in the class header. It is done like this:
Q_DECRARE_METATYPE(AAA);
- When using the property, in order not to “rob” problems (usually they are defined at the compiler level, so do not miss it), you need to register for the class somewhere in the application via the
qRegisterMetaType("AAA")
command
It is necessary to reassign for the used streaming classes, that is, to specify how to send them to the stream. This is done via the qRegisterMetaTypeStreamOperators ( const char * typeName )
. Description of the functions to be implemented in the class:
QDataStream &operator<<(QDataStream &out, const MyClass &myObj);
QDataStream &operator>>(QDataStream &in, MyClass &myObj);
After all these actions, we will be able to operate with properties-objects, not only the values ​​of standard types, and not use references to these types of types (see flaws).
Using dynamically created instances
The Qt property system supports this, though again, not “straight”, it is necessary to perform gestures. The class (base) must meet the requirements in the above list (well, unless item 4 is no longer required), plus more (thanks for the
fuCtor hint):
1. The constructor used must have the Q_INVOKABLE macro in its description
2. You need to use the command
QMetaObject :: newInstance () , clinging to the name of the class that can be passed along with the object (or its identifier) ​​QMetaObject. If MetaObject is not ready, you will have to be content with the default constructor (which nevertheless must meet the same requirements, and use
QMetaType :: construct , having received the registered type via
int QMetaType::type ( const char * typeName )
that
int QMetaType::type ( const char * typeName )
, and by Id (not forgetting to check whether the type is registered in the system by comparing with 0) already MetaType itself.
Advantages and disadvantages
Briefly outline the advantages of this method:
- Quick adaptation for new classes - write properties, functions for getting and installing them, follow up - and voila, serialization works
- The system supports both static and dynamic properties. If the client-server system at the other end, during deserialization, the object does not support the specified properties. name - it is created dynamically, and it can be traced anyway (though the setter / getter will not appear out of nowhere. We will not lose the data)
- It is working :)
But the shortcomings identified at the moment:
- Impossibility of correct transfer of properties-links. Addresses are transmitted, but the data for the links, no. Physically, pointer properties do not forbid us to do anything, but serialization with them will not work correctly.
- Non-standard property types must be registered with Q_DECLARE_METATYPE in order to be able to be operated with them through QVariant
- If you try to use the objects themselves in the view properties, you need to write a lot of additional code in the class — constructors, macros, and so on. True, in the end it translates into greater versatility.