Для вывода Painting мы просто выводим две строки типа QString и значение типа quint32. В конце функции мы возвращаем поток. Этот обычный в С++ прием позволяет использовать последовательность операторов << для вывода данных в поток. Например:
out << painting1 << painting2 << painting3;
Реализация оператора >> аналогична реализации оператора <<.
01 QDataStream &operator >> (QDataStream ∈, Painting &painting)
02 {
03 QString title;
04 QString artist;
05 quint32 year;
06 in >> title >> artist >> year;
07 painting = Painting(title, artist, year);
08 return in;
09 }
Обеспечение в пользовательских типах данных операторов ввода—вывода в поток дает несколько преимуществ. Одно из них заключается в том, что это позволяет нам выводить в поток контейнеры с пользовательскими типами. Например:
QList paintings = …;
out << paintings;
Мы можем так же просто считывать контейнеры:
QList paintings;
in >> paintings;
Это привело бы к ошибке компиляции, если бы тип Painting не поддерживал операции << или >>. Еще одно преимущество обеспечения потоковых операторов в пользовательских типах заключается в возможности хранения этих типов в виде объектов QVariant, что расширяет возможности их применения, например, в объектах QSettings. Это будет работать при условии предварительной регистрации типа с помощью функции qRegisterMetaTypeStreamOperators, работа которой рассматривается в главе 11.
При использовании QDataStream Qt обеспечивает чтение и запись каждого типа, включая контейнеры с произвольным числом элементов. Это освобождает нас от структурирования того, что мы записываем, и от выполнения какого бы то ни было синтаксического анализа того, что мы считываем. Необходимо лишь гарантировать чтение всех типов в той же последовательности, в какой они были записаны, предоставляя Qt обработку всех деталей.
QDataStream имеет смысл использовать как для своих собственных пользовательских форматов файлов, так и для стандартных двоичных форматов. Мы можем считывать и записывать стандартные форматы двоичных данных, используя потоковые операторы для базовых типов (например, quint16 или float) или при помощи функций readRawBytes и writeRawBytes. Если QDataStream используется только для чтения и записи «чистых» типов данных С++, нет необходимости вызывать функцию setVersion.
До сих пор мы загружали и сохраняли данные, жестко задавая в программе версию потока QDataStream::Qt_4_1. Этот подход прост, и он надежно работает, но он имеет один небольшой недостаток: мы не сможем воспользоваться новыми форматами и обновленными версиями форматов. Например, если в более поздней версии Qt добавится новый атрибут к QFont (кроме размера точки, наименования шрифта и так далее) и мы жестко закодируем номер версии Qt_4_1, этот атрибут не будет сохраняться и загружаться. Существует два решения. Первое решение заключается во включении номера версии QDataStream в файл:
QDataStream out(&file);
out << quint32(MagicNumber) << quint16(out.version);
(MagicNumber — это константа, которая уникально идентифицирует тип файла.) В этом случае мы всегда будем записывать данные с применением последней версии QDataStream (каким бы результат ни был). При считывании файла мы считываем номер версии потока:
01 quint32 magic;
02 quint16 streamVersion;
03 QDataStream in(&file);
04 in >> magic >> streamVersion;
05 if (magic != MagicNumber) {
06 cerr << "File is not recognized by this application" << endl;
07 return false;
08 } else if (streamVersion > in.version) {
09 cerr << "File is from a more recent version of the application"
10 << endl;
11 return false;
12 }
13 in.setVersion(streamVersion);
Мы можем считывать данные, если версия потока меньше или совпадает с версией, используемой в приложении; в противном случае мы выдаем сообщение об ошибке.