Work with QDataStream
I often had to work with the QDataStream class. As a result, I gained some experience on how to use it correctly.
I repeatedly noticed that at the beginning of working with a class there is an opinion that since a class has a stream in its name, it is simply obliged to store data. Help line QDatastream :: QDatastream (QByteArray * a, QIODevice :: OpenMode mode)
Constructs a data stream that operates on a byte array, a. The mode describes how the device is to be used.at first, few are worried. But if you look under the hood, you can see that, no data is written directly to QDataStream. The constructor initializes the QBuffer class, which in this case is a wrapper for the passed QByteArray. The idea is this: the data is stored exclusively in QByteArray, and all operations on (de) serialization are carried out by the QDataStream class. When reading data from a stream, only the pointer to the current byte changes, while the data itself is not lost. When writing, the data for QIODevice :: WriteOnly is overwritten with new values, for QIODevice :: Append they are added to the end. The conclusion about QByteArray lifetime control follows from this.
The record is represented by the standard operator << defined for all main 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.
Here, everything is quite simple: a record of a “complex” structure is divided into a record of simpler types. Also pay attention to QDataStream writeRawData (const char * s, int len) . It would be possible to write the values of arrays in a loop, but why do it if there is a more elegant way. In the same way, we overload the statement to read:
Everything is the same here, but you should pay attention to the function QDataStream :: setFloatingPointPrecision (FloatingPointPrecision precision) . The fact is that starting with Qt 4.6, you need to explicitly specify the precision of a floating-point type. As you might guess, SinglePrecision is needed for types with single precision, and DoublePrecision is for types with double precision . There are two ways to solve this unpleasant situation: the first is to reload << and >> something like this:
Or, in your code, indicate before reading float and double how to read them. By default, DoublePrecision is used.
Now let's pay 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 order of recording the high bit. The QDataStream :: setByteOrder (ByteOrder bo) method sets the order. With ByteOrder :: BigEndian, the record goes byte high, and this is the default order. With ByteOrder :: LittleEndian, the record goes the least significant bit forward.
In addition to the standard C ++ types, QDataStream also allows you to write some Qt classes such as QList and QVariant. However, there are hidden some problems associated with the Qt version. However, the developers took care of serializing classes of different versions. The QDataStream :: setVersion (int v) method , where v is the version of Qt, is responsible for this . Nevertheless, it is worth remembering that, if possible, to drag classes through different versions, only those properties of the class that are in the current version of the library will be available. You can get the version that the stream works with using QDataStream :: version () . consider a small example of writing to a QHash container file.
Before moving on to an example, I would like to pay special attention to such a thing as flush (). When working with some devices, data can be buffered, which means that writing to them may not occur at the time of calling the write function. The topic of buffering deserves a separate article, so let us dwell on the conclusion that we must forcefully empty the buffer for recording.
In the example, we created the simpleClass class and overloaded the QDataStream operators for it. Note that we made the operators class-friendly so that we could directly access private sections. It would be possible to fence the garden by overloading operators for the class or writing functions for accessing private properties, but to me personally, the solution with friend seems more elegant. Then everything is quite simple: open the file for reading, create a stream with a file binding, fill in QHash and write, without forgetting to specify the version of Qt. The order for reading is practically no different.
Despite the high level of class design, it also has its pitfalls to keep in mind. 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, a good tone will explicitly indicate the parameters of work with the stream. A couple of lines will save a lot of nerves in the future for those who have to work with the code after you.
Introduction
I repeatedly noticed that at the beginning of working with a class there is an opinion that since a class has a stream in its name, it is simply obliged to store data. Help line QDatastream :: QDatastream (QByteArray * a, QIODevice :: OpenMode mode)
Constructs a data stream that operates on a byte array, a. The mode describes how the device is to be used.at first, few are worried. But if you look under the hood, you can see that, no data is written directly to QDataStream. The constructor initializes the QBuffer class, which in this case is a wrapper for the passed QByteArray. The idea is this: the data is stored exclusively in QByteArray, and all operations on (de) serialization are carried out by the QDataStream class. When reading data from a stream, only the pointer to the current byte changes, while the data itself is not lost. When writing, the data for QIODevice :: WriteOnly is overwritten with new values, for QIODevice :: Append they are added to the end. The conclusion about QByteArray lifetime control follows from this.
Read Write
The record is represented by the standard operator << defined for all main 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;
}
Here, everything is quite simple: a record of a “complex” structure is divided into a record of simpler types. Also pay attention to QDataStream writeRawData (const char * s, int len) . It would be possible to write the values of arrays in a loop, but why do it if there is a more elegant way. In the same way, we overload the statement to read:
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 function QDataStream :: setFloatingPointPrecision (FloatingPointPrecision precision) . The fact is that starting with Qt 4.6, you need to explicitly specify the precision of a floating-point type. As you might guess, SinglePrecision is needed for types with single precision, and DoublePrecision is for types with double precision . There are two ways to solve this unpleasant situation: the first is to reload << 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. By default, DoublePrecision is used.
Now let's pay 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 order of recording the high bit. The QDataStream :: setByteOrder (ByteOrder bo) method sets the order. With ByteOrder :: BigEndian, the record goes byte high, and this is the default order. With ByteOrder :: LittleEndian, the record goes the least significant bit forward.
(De) Serialization of Qt classes
In addition to the standard C ++ types, QDataStream also allows you to write some Qt classes such as QList and QVariant. However, there are hidden some problems associated with the Qt version. However, the developers took care of serializing classes of different versions. The QDataStream :: setVersion (int v) method , where v is the version of Qt, is responsible for this . Nevertheless, it is worth remembering that, if possible, to drag classes through different versions, only those properties of the class that are in the current version of the library will be available. You can get the version that the stream works with using QDataStream :: version () . consider a small example of writing to a QHash container file.
#include
#include
#include
#include
#include
#include
#include
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 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 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 moving on to an example, I would like to pay special attention to such a thing as flush (). When working with some devices, data can be buffered, which means that writing to them may not occur at the time of calling the write function. The topic of buffering deserves a separate article, so let us dwell on the conclusion that we must forcefully empty the buffer for recording.
In the example, we created the simpleClass class and overloaded the QDataStream operators for it. Note that we made the operators class-friendly so that we could directly access private sections. It would be possible to fence the garden by overloading operators for the class or writing functions for accessing private properties, but to me personally, the solution with friend seems more elegant. Then everything is quite simple: open the file for reading, create a stream with a file binding, fill in QHash and write, without forgetting to specify the version of Qt. The order for reading is practically no different.
Conclusion
Despite the high level of class design, it also has its pitfalls to keep in mind. 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, a good tone will explicitly indicate the parameters of work with the stream. A couple of lines will save a lot of nerves in the future for those who have to work with the code after you.