📜 ⬆️ ⬇️

Serialization in Qt through the use of MetaObject

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
  1. either inheriting from the base class and expanding it
  2. 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:
  1. 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.
  2. Register the metaclass in the class header. It is done like this: Q_DECRARE_METATYPE(AAA);
  3. 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:


But the shortcomings identified at the moment:

Source: https://habr.com/ru/post/83374/


All Articles