Работа с QDataStream

в 8:07, , рубрики: c++, qt, Qt Software, метки: ,

Приходилось часто работать с классом QDataStream. В результате накопил некоторый опыт, как правильно его использовать.

Введение

Неоднократно замечал, что в начале работы с классом бытует мнение, что раз класс имеет в своем названии stream, то просто обязан хранить данные. Строчка в помощи 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.
поначалу мало у кого вызывает опасения. Но если взглянуть под капот, то можно увидеть что, никакие данные непосредственно в QDataStream не записываются. В конструкторе инициализируется класс QBuffer, который является в данном случае оберткой для переданного QByteArray’a. Замысел работы такой: данные хранятся исключительно в QByteArray, а все операции по (де)сериализации проводит класс QDataStream. При чтении данных из потока изменяется лишь указатель на текущий байт, при этом сами данные не теряются. При записи данные для QIODevice::WriteOnly затираются новыми значениями, для QIODevice::Append добавляются в конец. Из этого следует вывод о контроле времени жизни QByteArray.

Чтение-Запись

Запись представлена стандартным оператором << определенный для всех основных типов. Однако, зачастую удобней записывать данные сразу в структуру данных. Следующий пример показывает как перегрузить оператор << для наших целей

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;
}

Здесь все довольно просто: запись «сложной» структуры разбивается на запись более простых типов. Так же обратите внимание на QDataStream writeRawData(const char* s, int len). Можно было бы в цикле записать значения массивов, но зачем это делать, если есть более элегантный способ.Точно так же перегрузим оператор для чтения:

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;
}

Здесь все тоже самое, но стоит обратить внимание на функцию QDataStream::setFloatingPointPrecision (FloatingPointPrecision precision). Дело в том, что начиная с версии Qt 4.6, требуется явно указывать точность типа с плавающей точкой. Как можно догадаться SinglePrecision нужен для типов с одинарной точностью, а DoublePrecision для типов с двойной. Для решения этой неприятной ситуации есть два пути: первый это перегрузить << и >> примерно так:

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

Или же в своем коде указывать перед чтением float и double как их считывать. По умолчанию используется DoublePrecision.
Теперь обратим внимание на QDataStream::skipRawData(int len). Данная функция просто пропускает указанное количество байт, что бывает крайне полезно при выравнивании структур.

Кроме стандартных типов С++ QDataStream позволяет записывать также некоторые классы Qt такие как QList и QVariant. Однако тут скрыта некоторые проблемы связанные с версией Qt. Однако, разработчики позаботились о сериализации классов различных версий. Ответственен за это метод QDataStream::setVersion ( int v ), где v указание версии Qt. Тем не менее, стоит помнить, что при возможности протащить классы через различные версии, будут доступны только те свойства класса, которые есть в актуальной версии библиотеки. Получить версию, с которой работает поток, можно при помощи QDataStream::version ().

Отдельно стоит сказать про порядок записи старшего бита. Метод QDataStream::setByteOrder( ByteOrder bo) устанавливает порядок следования. При ByteOrder::BigEndian запись идет старшим байтом вперед, и именно такой порядок применяется по умолчанию. При ByteOrder::LittleEndian запись идет младшим битом вперед.

Заключение

Несмотря на высокий уровень проектирования класса в нем тоже есть свои подводные камни, о которых надо помнить. Следует всегда помнить, что класс является функциональной оберткой над структурой данных и пользоваться им имеет смысл, когда требуется непосредственно производить операции чтения/записи, а для передачи данных использовать другие средства. Кроме того, хорошим тоном будет явно указывать параметры работы с потоком. Пара строчек сэкономит в будущем много нервов тем, кому придется работать с кодом после вас.

Автор: Arakel

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js