📜 ⬆️ ⬇️

Work with QDataStream

It was necessary to work often with the QDataStream class. As a result, I gained some experience how to use it correctly.


Introduction


I repeatedly noticed that at the beginning of work with a class there is an opinion that once a class has stream in its name, it is simply obliged to store data. Line in QDatastream :: QDatastream help (QByteArray * a, QIODevice :: OpenMode mode)
Constructs a data stream that operates on a byte array, a. This is what device is used to be. At first, few people have concerns. But if you look under the hood, you can see that no data directly in the QDataStream is written. In the constructor, the QBuffer class is initialized, which in this case is a wrapper for the passed QByteArray. The idea of ​​work is as follows: data is stored exclusively in QByteArray, and all operations on (de) serialization are performed by the QDataStream class. When reading data from a stream, only the pointer to the current byte is changed, while the data itself is not lost. When writing, data for QIODevice :: WriteOnly is overwritten by new values, for QIODevice :: Append it is added to the end. From this follows the conclusion about the QByteArray lifetime monitoring.

Read-Write


The entry is represented by the standard operator << defined for all basic types. However, it is often more convenient to write data directly to the data structure. The following example shows how to overload the operator << for our purposes.

sctruct anyStruct { short sVal; float fVal; double dVal; short Empty; char array[8]; } QDataStream operator <<(QDataStream &out, const anyStruct &any) { out << any.sVal; out << any.fVal; out << any.dVal; out << any.Empty; out.writeRawData(any.array,sizeof(any.array)); return out; } 

')
Everything is quite simple here: the entry of a “complex” structure is divided into the entry of simpler types. Also note the QDataStream writeRawData (const char * s, int len) . It would be possible to write the values ​​of the arrays in a loop, but why do it if there is a more elegant way. In the same way, we will overload the operator for reading:

 QDataStream operator >>(QDataStream &out, anyStruct &any) { out >> any.sVal; out.setFloatingPointPrecision(QDataStream::FloatingPointPrecision); out >> any.fVal; out.setFloatingPointPrecision(QDataStream::DoublePrecision); out >> any.dVal; out.skipRawData(sizeof(any.Empty)); out.ReadRawData(any.array,sizeof(any.array)); return out; } 


Everything is the same here, but you should pay attention to the QDataStream :: setFloatingPointPrecision (FloatingPointPrecision precision) function. The point is that starting with Qt 4.6, you need to explicitly specify the precision of the floating-point type. As you can guess, SinglePrecision is needed for types with single precision, and DoublePrecision for types with double. There are two ways to solve this unpleasant situation: the first is to overload << and >> something like this:

 QDataStream operator >>(QDataStream &out, float &val) { if(out. FloatingPointPrecision() != QDataStream:: SinglePrecision) { out.setFloatingPointPrecision(QDataStream::FloatingPointPrecision); out >> val; out.setFloatingPointPrecision(QDataStream::DoublePrecision); } } 


Or, in your code, indicate before reading float and double how to read them. The default is DoublePrecision.
Now let's turn our attention to QDataStream :: skipRawData (int len) . This function simply skips the specified number of bytes, which is extremely useful when aligning structures.

Separately, it should be said about the recording order of the high bit. The QDataStream :: setByteOrder (ByteOrder bo) method sets the order. With ByteOrder :: BigEndian, the record goes high byte forward, and that is the order used by default. With ByteOrder :: LittleEndian, the record goes in the low- order bit.

(De) Serialization of Qt classes


In addition to standard C ++ types, QDataStream also allows you to write some Qt classes such as QList and QVariant. However, there are some problems with the Qt version. However, the developers took care of the serialization of classes of different versions. Responsible for this is the QDataStream :: setVersion (int v) method, where v is the Qt version indication. Nevertheless, it is worth remembering that if you can push classes through different versions, only those class properties that are in the current version of the library will be available. You can get the version the stream works with using QDataStream :: version () . Consider a small example of writing to a QHash container file.

 #include <QtCore/QCoreApplication> #include <QDataStream> #include <QByteArray> #include <QFile> #include <QString> #include <QHash> #include <QDebug> class simpleClass { public: quint32 a,b; quint32 func(quint32 arg1, quint32 arg2); quint32 getC(); friend QDataStream &operator <<(QDataStream &stream,const simpleClass &sC); friend QDataStream &operator >>(QDataStream &stream, simpleClass &sC); protected: quint32 c; }; inline quint32 simpleClass::func(quint32 arg1, quint32 arg2) { a = arg1; b = arg2; c = a+b; return c; } inline quint32 simpleClass::getC() { return c; } inline QDataStream &operator <<(QDataStream &stream,const simpleClass &sC) // ; { stream << sC.a; stream << sC.b; stream << sC.c; return stream; } inline QDataStream &operator >>(QDataStream &stream, simpleClass &sC) // ; { stream >> sC.a; stream >> sC.b; stream >> sC.c; return stream; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QFile appFile(QString("filename.data")); appFile.open(QFile::Append); //    ; QDataStream inFile(&appFile); //     QIODevice; QHash <quint32,simpleClass> hash; //       ; for(quint32 i = 0; i < 64; i++) { if(!hash.contains(i)) //    ,    ; { simpleClass sC; //  ; sC.func(i,i+10); //   ; hash.insert(i,sC); //   ; } } inFile.setVersion(QDataStream::Qt_4_8); //    Qt,  ; inFile << hash; //  ; appFile.flush(); //     ; appFile.close(); //      ; QFile readFile(QString("filename.data")); readFile.open(QFile::ReadOnly); QDataStream outFile(&readFile); outFile.setVersion(QDataStream::Qt_4_8); QHash<quint32,simpleClass> readHash; //   ; outFile >> readHash; //     ; foreach(quint64 key, readHash.keys()) //   : readHash.keys()    ; { simpleClass sC = readHash.value(key); //     ; qDebug() << "Sum was " << sC.getC(); //   ; qDebug() << "Sum is "<< sC.func(key,2*key); //   ; } readFile.close(); return a.exec(); } 


Before going to the example, I want to pay special attention to such a thing as flush (). When working with some devices, the data can be buffered, which means that writing to them may not happen at the moment of calling the recording function. The topic of buffering deserves a separate article, so let us dwell on the conclusion that it is necessary to forcibly empty the buffer for writing.

In the example, we created the class simpleClass and overloaded the QDataStream operators for it. Notice that we made the operators friendly to the class in order to be able to directly access the private sections. It would be possible to make a fuss overloading operators for a class or writing functions for accessing private properties, but for me personally, a solution with a friend seems more elegant. Then everything is pretty simple: open the file for reading, create a stream with a binding to the file, fill in QHash and write it down, not forgetting to specify the version of Qt. The order for reading is almost the same.

Conclusion


Despite the high level of design class, it also has its pitfalls, which must be remembered. It should always be remembered that a class is a functional wrapper over a data structure and it makes sense to use it when you need to directly perform read / write operations, and use other means to transfer data. In addition, it is good practice to explicitly specify the parameters for working with a stream. A couple of lines will save a lot of nerves in the future for those who will have to work with the code after you.

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


All Articles