Программирование с Qt: Часть 2. Типы, варианты, ссылки и разделение данных
Источник: IBM developerWorks Россия
Автор: Алексей Бешенов, технический писатель, независимый специалист
Уровень сложности: средний
В этой статье:
- typedef в Qt
- Регистрация типов
- Варианты
- Защищенные указатели
- Подсчет ссылок
- Разделение данных
- Загрузка
- Ресурсы
- Об авторе
Qt – кроссплатформенный инструментарий для разработки прикладного программного обеспечения, широко используемый при создании графических пользовательских интерфейсов. В первом материале цикла мы рассмотрели основные инструменты разработчика, а также используемую в Qt объектную модель. Теперь перейдем к вопросам, касающиеся типов, вариантов, подсчета ссылок и разделения данных, поскольку без этих базовых сведений работать с инструментарием невозможно.
1. typedef в Qt
В Qt имеются директивы typedef, облегчающие кроссплатформенную разработку и написание имен типов (таблица 1).
Таблица 1. typedef в Qt
Для переносимости кода | |
---|---|
qint8 | 8-битное знаковое целое |
quint8 | 8-битное беззнаковое целое |
qint16 | 16-битное знаковое целое |
quint16 | 16-битное беззнаковое целое |
qint32 | 32-битное знаковое целое |
quint32 | 32-битное беззнаковое целое |
qint64 | 64-битное знаковое целое |
quint64 | 64-битное беззнаковое целое |
quintptr | Беззнаковый указатель (32 или 64 бита) |
qptrdiff | Знаковый указатель (32 или 64 бита) |
qreal | Везде double; float только для архитектуры ARM |
Для краткой записи типов | |
uchar | unsigned char |
uint | unsigned int |
ulong | unsigned long |
ushort | unsigned short |
qlonglong | long long int или __int64 |
qulonglong | unsigned long long int или unsigned __int64 |
В большинстве систем qint8, qint16, qint8 соответствуют signed char, signed short и signed int.
Тип qint64 обычно соответствует long long int. В компиляторах, не использующих расширения C99 (например, msvc), этот тип может обозначаться как __int64 (также используются __int8, __int16, __int32).
Для повышения переносимости кода 64-битные литералы можно записывать через макросы Q_INT64_C и
Q_UINT64_C: qint64 m = Q_INT64_C(9223372036854775807); quint64 n = Q_UINT64_C(18446744073709551615); |
Это то же самое, что 9223372036854775807LL и 18446744073709551615ULL.
Для указателей гарантируется
sizeof(void*) == sizeof(quintptr) && sizeof(void*) == sizeof(qptrdiff) |
2. Регистрация типов
Для эффективной работы с данными (в частности, в контейнерах) определенному коду требуется информация о типах. В Qt ее можно указать при помощи Q_DECLARE_TYPEINFO:
Q_DECLARE_TYPEINFO (Type, Flags) |
где Type – тип, Flags – флаг:
- Q_PRIMITIVE_TYPE – примитив без конструктора и деструктора;
- Q_MOVABLE_TYPE – тип с конструктором и/или деструктором, который можно перемещать в памяти при помощи memcpy();
- Q_COMPLEX_TYPE – сложный тип с конструктором и/или деструктором, который нельзя перемещать в памяти.
Примеры примитивных типов: bool, double, qint64.
Многие классы Qt относятся к Q_MOVABLE_TYPE: QBitArray, QChar, QDate, и т.д. Например, QPoint:
class QPoint { public: QPoint(); QPoint(int xpos, int ypos); // ... private: int xp; int yp; }; |
По умолчанию (если в коде нет Q_DECLARE_TYPEINFO) подразумевается Q_COMPLEX_TYPE.
Например, в контейнере Qvector<T> при увеличении вектора используется realloc(), если тип T может перемещаться в памяти.
При вставке в середину вектора элементы за точкой вставки нужно сдвинуть на одну позицию вперед. Если T можно перемещать в памяти, применяется memmove(). Иначе требуется присваивание (с оператором =).
Также, если указано Q_PRIMITIVE_TYPE, токонтейнер не будет вызывать конструктор и деструктор.
Контейнеры мы подробно рассмотрим в отдельной статье.
![]() |
|
3. Варианты
Объединение (union) в C и C++ – это структура, все члены которой располагаются по одному и тому же адресу. Таким образом, под объединение отводится столько места в памяти, сколько занимает его наибольший член. Никакого контроля за тем, что находится в объединении не ведется, и его членами не могут быть объекты класса со специальным конструктором, деструктором, либо операцией копирования.
Варианты, реализованные через класс QVariant, могут хранить одновременно одно значение определенного типа, но являются более удобными и безопасными, а также не предполагают жестких ограничений, как объединения.
Все возможные типы определяет перечисление QVariant::Type:
class QVariant { enum Type { // Неправильный тип: Invalid = 0, // Классы из модуля QtCore и примитивные типы BitArray = 13, // QBitArray Bool = 1, // bool ByteArray = 12, // QByteArray Char = 7, // QChar Date = 14, // QDate DateTime = 16, // QDateTime Double = 6, // double Hash = 28, // QHash<QString, QVariant> Int = 2, // int Line = 23, // QLine LineF = 24, // QLineF List = 9, // QList<QVariant> Locale = 18, // QLocale LongLong = 4, // qlonglong Map = 8, // QMap<QString, QVariant> Point = 25, // QPoint PointF = 26, // QPointF Rect = 19, // QRect RectF = 20, // QRectF RegExp = 27, // QRegExp Size = 21, // QSize SizeF = 22, // QSizeF String = 10, // QString StringList = 11, // QStringList Time = 15, // QTime UInt = 3, // uint ULongLong = 5, // qulonglong Url = 17, // QUrl // Классы из модуля QtGui Bitmap = 73, // QBitmap Brush = 66, // QBrush Color = 67, // QColor Cursor = 74, // QCursor Font = 64, // QFont Icon = 69, // QIcon Image = 70, // QImage KeySequence = 76, // QKeySequence Matrix = 80, // QMatrix Transform = 81, // QTransform Palette = 68, // QPalette Pen = 77, // QPen Pixmap = 65, // QPixmap Polygon = 71, // QPolygon Region = 72, // QRegion SizePolicy = 75, // QSizePolicy TextFormat = 79, // QTextFormat TextLength = 78, // QTextLength // Базовое значение для пользовательских типов: UserType = 127 }; // ... }; |
Для контейнеров имеются удобные короткие синонимы:
typedef QList<QVariant> QVariantList; typedef QMap<QString, QVariant> QVariantMap; typedef QHash<QString, QVariant> QVariantHash; |
3.1. Создание вариантов
Вариант может быть нулевым, а также неправильным. Для нулевого варианта задается тип, но не задается значение, у неправильного варианта нет типа.
Конструктор по умолчанию QVariant создает неправильный вариант. Если указать в конструкторе QVariant::Type, то будет создан нулевой вариант данного типа.
Нулевой и неправильный варианты можно определить при помощи методов isNull() и isValid().
bool QVariant::isNull() const; bool QVariant::isValid() const; |
Пример:
QVariant().isValid(); // false QVariant(QVariant::Int).isNull(); // true QVariant(0).isNull(); // false |
Вторым аргументом передается указатель на объект, которым инициализируется вариант.
QVariant::QVariant (Type type); QVariant::QVariant (int typeOrUserType, const void* copy); QVariant::QVariant (const QVariant& p); |
Пример:
QVariant var0(QVariant::Point); // нулевой вариант QPoint p(23,23); QVariant var1(QVariant::Point, &p); |
В конструкторе QVariant можно передать ссылку на объект любого из указанных выше классов из модуля QtCore, либо примитивный тип (const QBitArray&, bool, const QByteArray&, ...):
QVariant var2(3.1415926); QVariant var3(true); QList<QVariant> list; list << QVariant(1) << QVariant(2) << QVariant(3); // (О контейнерах мы расскажем отдельно) QVariant var4(list); |
QtGui – это уже другой модуль, и для аргументов его классов (QBitmap, QBrush, QColor, ...) схожих конструкторов нет.
3.2. Пользовательские типы в вариантах
Для хранения пользовательских типов используется класс QMetaType. Размещенные в QVariant типы должны регистрироваться через макрос Q_DECLARE_METATYPE:
namespace Foo { struct Bar { int baz; char quux; }; } Q_DECLARE_METATYPE(Foo::Bar) |
Обратите внимание, что макрос размещен вне пространства имен, а тип указан полностью (Foo::Bar). Теперь Foo::Bar можно использовать в вариантах:
Foo::Bar bar; bar.baz = 23; bar.quux = 'a'; QVariant var; var.setValue<Foo::Bar>(bar); // ... Foo::Bar bar2 = var.value<Foo::Bar>(); var.typeName(); // Foo::Bar |
Желательно, чтобы такая регистрация типа происходила сразу после его объявления. Тогда не нужно следить, чтобы он использовался в вариантах только после Q_DECLARE_METATYPE.
Тип может быть любым классом или структурой с конструктором по умолчанию, конструктором копирования и деструктором, имеющими модификатор доступа public.
Зарегистрированным типам присваиваются целочисленные идентификаторы, начиная с 256. Идентификатор типа T можно получить при помощи функций
template<typename T> int qMetaTypeId(); static int QMetaType::type (const char* typeName); |
qMetaTypeId() работает на стадии компиляции, а QMetaType::type() – на стадии выполнения, поэтому допускает несуществующие типы, для которых просто возвращает 0.
В перечислении QMetaType::Type определены идентификаторы для встроенных типов:
class QMetaType { enum Type { Bool = 1, // bool Char = 131, // char Double = 6, // double Float = 135, // float Int = 2, // int Long = 129, // long LongLong = 4, // LongLong QBitArray = 13, // QBitArray QBitmap = 73, // QBitmap QBrush = 66, // QBrush QByteArray = 12, // QByteArray QChar = 7, // QChar QColor = 67, // QColor QColorGroup = 63, // QColorGroup QCursor = 74, // QCursor QDate = 14, // QDate QDateTime = 16, // QDateTime QFont = 64, // QFont QIcon = 69, // QIcon QImage = 70, // QImage QKeySequence = 76, // QKeySequence QLine = 23, // QLine QLineF = 24, // QLineF QLocale = 18, // QLocale QMatrix = 80, // QMatrix QObjectStar = 136, // QObject* QPalette = 68, // QPalette QPen = 77, // QPen QPixmap = 65, // QPixmap QPoint = 25, // QPoint QPointF = 26, // QPointF QPolygon = 71, // QPolygon QRect = 19, // QRect QRectF = 20, // QRectF QRegExp = 27, // QRegExp QRegion = 72, // QRegion QSize = 21, // QSize QSizeF = 22, // QSizeF QSizePolicy = 75, // QSizePolicy QString = 10, // QString QStringList = 11, // QStringList QTextFormat = 79, // QTextFormat QTextLength = 78, // QTextLength QTime = 15, // QTime QTransform = 81, // QTransform QUrl = 17, // QUrl QVariantHash = 28, // QVariantHash QVariantList = 9, // QVariantList QVariantMap = 8, // QVariantMap QWidgetStar = 137, // QWidget* Short = 130, // short UChar = 134, // unsigned char UInt = 3, // unsigned int ULong = 132, // unsigned long ULongLong = 5, // ULongLong UShort = 133, // unsigned short Void = 0, // void VoidStar = 128, // void* // Базовое значение для пользовательских типов: User = 256 }; // ... }; |
Для использования своих типов в вариантах достаточно макроса Q_DECLARE_METATYPE.
В дополнение к регистрации типа через макрос, для его использования с шаблонами функций (такими как QVariant::setValue<T>() и QVariant::value<T>()), имеется регистрация на стадии исполнения через
template<typename T> int qRegisterMetaType(); |
qRegisterMetaType возвращает идентификатор, используемый QMetaType. Эта функция должна вызываться после того, как тип T зарегистрирован при помощи Q_DECLARE_METATYPE.
После этого становятся доступными функции, действующие на стадии исполнения.
При помощи QMetaType можно динамически вызывать конструкторы и деструкторы:
int id = qRegisterMetaType<Foo::Bar>(); Foo::Bar* p_bar = (Foo::Bar*)QMetaType::construct(id, "Foo::Bar"); if (p_bar != (void*)(-1)) { p_bar->baz = 23; p_bar->quux = 'a'; // ... qDebug() << p_bar->baz; qDebug() << p_bar->quux; } else { qFatal("Can't construct Foo::Bar"); } QMetaType::destroy(id, p_bar); |
3.3. Операции над вариантами
Чтобы узнать тип, можно использовать следующие методы:
Type QVariant::type() const; int QVariant::userType() const; const char* QVariant::typeName() const; static const char* QVariant::typeToName (Type type); |
Для пользовательских типов userType() возвращает зарегистрированный идентификатор, а type() – значение QVariant::UserType. Для типов из QVariant разницы между userType() и type() нет.
Прежде чем извлекать из варианта значение, стоит проверить, допустима ли данная операция:
bool QVariant::canConvert (Type type) const; template<typename T> bool QVariant::canConvert() const; |
Для вариантов из предыдущего примера:
var1.canConvert(QVariant::String); // false var2.canConvert(QVariant::Int); // true var3.canConvert(QVariant::Int); // true var3.canConvert(QVariant::Date); // false |
То же самое через шаблон метода:
var1.canConvert<QString>(); // false var2.canConvert<int>(); // true var3.canConvert<int>(); // true var3.canConvert<QDate>(); // false |
В таблице 2 перечислены возможные преобразования.
Таблица 2. Автоматическое преобразование типов в QVariant
Тип | Автоматически преобразуется в |
---|---|
Bool | Char, Double, Int, LongLong, String, UInt, ULongLong |
ByteArray | Double, Int, LongLong, String, UInt, ULongLong |
Char | Bool, Int, UInt, LongLong, ULongLong |
Color | String |
Date | DateTime, String |
DateTime | Date, String, Time |
Double | Bool, Int, LongLong, String, UInt, ULongLong |
Font | String |
Int | Bool, Char, Double, LongLong, String, UInt, ULongLong |
KeySequence | Int, String |
List | StringList |
LongLong | Bool, ByteArray, Char, Double, Int, String, UInt, ULongLong |
Point | PointF |
Rect | RectF |
String | Bool, ByteArray, Char, Color, Date, DateTime, Double, Font, Int, KeySequence, LongLong, StringList, Time, UInt, ULongLong |
StringList | List, String |
Time | String |
UInt | Bool, Char, Double, Int, LongLong, String, ULongLong |
ULongLong | Bool, Char, Double, Int, LongLong, String, UInt |
Стоит отметить, что проверяется лишь принципиальная возможность преобразования типов. Например, строка может быть преобразована в целое число, но не каждая строка описывает целое число.
Значения получают следующими методами:
QBitArray QVariant::toBitArray() const; bool QVariant::toBool() const; QByteArray QVariant::toByteArray() const; QChar QVariant::toChar() const; QDate QVariant::toDate() const; QDateTime QVariant::toDateTime() const; QLine QVariant::toLine() const; QLineF QVariant::toLineF() const; QLocale QVariant::toLocale() const; QPoint QVariant::toPoint() const; QPointF QVariant::toPointF() const; QRect QVariant::toRect() const; QRectF QVariant::toRectF() const; QRegExp QVariant::toRegExp() const; QSize QVariant::toSize() const; QSizeF QVariant::toSizeF() const; QString QVariant::toString() const; QStringList QVariant::toStringList() const; QTime QVariant::toTime() const; QUrl QVariant::toUrl() const; QList<QVariant> QVariant::toList() const; QHash<QString, QVariant> QVariant::toHash() const; QMap<QString, QVariant> QVariant::toMap() const; double QVariant::toDouble (bool* ok = 0) const; int QVariant::toInt (bool* ok = 0) const; qlonglong QVariant::toLongLong (bool* ok = 0) const; uint QVariant::toUInt (bool* ok = 0) const; qulonglong QVariant::toULongLong (bool* ok = 0) const; |
Методам преобразования в числа можно передать указатель на булеву переменную чтобы проверить, удачно ли преобразовано значение.
var1.toPoint(); // QPoint(23,23) var2.toDouble(); // 3.1415926 var2.toString(); // "3.1415926" var2.toInt(); // 3 var3.toBool(); // true bool test; QVariant("foo").toInt(&test); // 0 test; // false |
Для типов из QtGui используется метод value():
template<typename T> T QVariant::value() const; |
Перепишем приведенный выше пример:
var1.value<QPoint>(); // QPoint(23,23) var2.value<double>(); // 3.1415926 var2.value<QString>(); // "3.1415926" var2.value<int>(); // 3 var3.value<bool>(); // true |
В случае неудачи возвращается значение по умолчанию (для чисел это 0, для классов вызывается конструктор по умолчанию).
Аналогично работает qvariant_cast:
template<typename T> T qvariant_cast (const QVariant& value); |
Для преобразования без создания нового варианта существует метод convert():
bool QVariant::convert (Type type); |
Если преобразование завершено успешно, тип меняется и возвращается true. Иначе возвращается false, а вариант становится неправильным:
var2.convert(QVariant::Int); // true var2.typeName(); // int var2.value<int>(); // 3 var2.convert(QVariant::Date); // false var2.isValid(); // false |
Для присвоения значения используйте setValue():
template<typename T> void QVariant::setValue (const T& value); template<typename T> static void QVariant::fromValue (const T& value); |
В setValue() значение копируется и сохраняется внутри варианта. Метод fromValue() делает то же самое с той лишь разницей, что он статический, и возвращает новый вариант.
Как и в STL, в Qt имеются шаблоны контейнеров. При помощи вариантов можно использовать рекурсивные структуры данных (листинг 1.1).
Листинг 1.1. представление вложенных списков через QVariant
void dumpList (const QVariantList& list, QTextStream& out) { out << "("; // (На вводе/выводе мы еще остановимся в другой статье) QVariantList::const_iterator i = list.constBegin(); // (На итераторах и контейнерах — тоже) while (i != list.constEnd()) { if ((*i).type() == QVariant::List) dumpList ((*i).toList(), out); else out << (*i).toString(); if (++i != list.constEnd()) out << " "; } out << ")"; } int main() { QVariantList list; list << QVariant("foo"); QVariantList sublist; sublist << QVariant("bar") << QVariant("baz") << QVariant(23) << QVariant(true); list << QVariant(sublist) << QVariant("quux") << QVariant(QVariantList()); QTextStream cout(stdout); dumpList (list, cout); // (foo (bar baz 23 true) quux ()) return 0; |
![]() |
|
4. Защищенные указатели
Когда объект, на который ссылается указатель, разрушается, указатель становится «повисшим», т.е. содержащим адрес, по которому уже нет объекта. Это часто служит источником ошибок.
В Qt имеется защищенный указатель QPointer<T>, который автоматически принимает значение 0 при разрушении связанного с ним объекта. Объект должен наследовать от QObject.
В остальном QPointer<T> работает как T*. Он автоматически приводится к T*, перегружаются операторы * и -> для разыменования, а также присваивание =.
Конструкторы QPointer<T>:
QPointer(); // нулевой QPointer (T *p); QPointer (const QPointer<T> &p); // копия |
Методы:
T* data() const; // указатель bool isNull() const; // true, если 0 |
Операторы (+, -, ++ и --), используемые для арифметики указателей, не перегружаются.
Пример:
#include <QPointer> QPointer<Foo> x = new Foo; // ... if (x) { x->bar(); // не выполняется, если x был удален } |
Разумеется, защищенные указатели предполагают дополнительные накладные расходы по сравнению с обычными. Оповещение об уничтожении объекта реализовано через подключение сигнала к слоту. Но обычно этим можно пренебречь.
![]() |
|
5. Подсчет ссылок
Классы QSharedPointer и QWeakPointer реализуют подсчет ссылок.
5.1. Жесткие ссылки
Обычный указатель типа T* можно «обернуть» в объект QSharedPointer<T>, который послужит жесткой ссылкой. Указатель будет удален в тот момент, когда последняя ссылка выйдет за область действия, и для нее будет вызван деструктор QsharedPointer<T>. Такая ссылка конструируется из обычного указателя:
QSharedPointer<Foo> sp0(new Foo); |
Если указатель на объект передан конструктору QSharedPointer<T>, то объект нельзя самостоятельно уничтожать либо создавать из него еще один QSharedPointer<T>. Новые жесткие ссылки создаются через копирование и присваивание:
Foo* foo = new Foo; QSharedPointer<Foo> sp0(foo); QSharedPointer<Foo> sp1(sp0); // правильно QSharedPointer<Foo> sp2; sp2 = sp0; // правильно QSharedPointer<Foo> sp3(foo); // ошибка! |
В конструкторе можно указать произвольную функцию (функтор) для удаления:
QSharedPointer::QSharedPointer (T *ptr, Deleter deleter); |
Как и для других умных указателей, здесь перегружены операторы разыменования (*), доступа к членам (->), а также сравнения (!= и ==). QSharedPointer<T> работает как обычный указатель T*.
QSharedPointer<Foo> sp0(new Foo); QSharedPointer<Foo> sp1(sp0); (*sp0).bar = 23; sp0->baz(); if (sp0 == sp1) { // ... } if (sp0.isNull()) // выполняется, если указатель нулевой { // ... } if (sp0 || !sp1) // используется приведение к bool { // и оператор ! // ... } |
Сам указатель можно получить при помощи метода
T* QSharedPointer::data() const; |
5.2. Слабые ссылки
Для слабых ссылок QWeakPointer<T> не задано разыменование. Они применяются только для того чтобы проверить, не был ли указатель удален. Аналогично, имеется метод isNull(), приведение к bool и оператор !. Слабые ссылки можно копировать, присваивать и сравнивать.
Допускается преобразование жесткой ссылки в слабую и наоборот:
QWeakPointer<T> QSharedPointer::toWeakRef() const; QSharedPointer<T> QWeakPointer::toStrongRef() const; |
При этом, слабые ссылки создаются только из жестких, как в случае
QWeakPointer<Foo> wp(sp0); |
5.3. Приведение
Для приведения типов предусмотрены вспомогательные функции. Если указатель на T0 нужно привести к указателю на T1 при помощи static_cast, const_cast, dynamic_cast, то используйте соответственно:
QSharedPointer<T1> qSharedPointerCast (const QSharedPointer<T0>& other); QSharedPointer<T1> qSharedPointerCast (const QWeakPointer<T0>& other); QSharedPointer<T1> qSharedPointerConstCast (const QSharedPointer<T0>& other); QSharedPointer<T1> qSharedPointerConstCast (const QWeakPointer<T0>& other); QSharedPointer<T1> qSharedPointerDynamicCast (const QSharedPointer<T0>& other); QSharedPointer<T1> qSharedPointerDynamicCast (const QWeakPointer<T0>& other); |
Эти функции принимают жесткую или слабую ссылку и возвращают жесткую. Преобразование из слабой ссылки в слабую при помощи static_cast осуществляет
QWeakPointer<T1> qWeakPointerCast (const QWeakPointer<T0>& other); |
Если преобразование dynamic_cast не удается, то возвращается ссылка, соответствующая нулевому указателю.
![]() |
|
6. Разделение данных
Объекты классов с разделяемыми данными без дополнительных накладок могут быть переданы по значению. При этом передаются только указатели на данные. Ведется подсчет ссылок, и данные удаляются, как только счетчик принимает значение 0.
При неявном разделении данных передаваемые указатели служат неглубокими копиями, а сами данные дублируются только при записи изменений (создаются глубокие копии). Присваивание создает неглубокую копию.
Явное разделение данных означает, что данные не копируются, а изменения затрагивают все объекты.
Иногда это называют семантикой значений и семантикой указателей.
6.1. Неявное разделение данных в Qt
В Qt данные неявно разделяются:
- вариантами QVariant;
- массивами битов QBitArray;
- массивами байтов QByteArray и строками QString;
- регулярными выражениями QRegExp;
- контейнерами: QHash, QLinkedList, QList, QMap, QMultiHash, QMultiMap, QQueue, QSet, QStack, QVector;
- классами, наследующими от контейнеров: QPolygon, QPolygonF, QStringList;
- кэшами QCache;
- URL QUrl;
- QLocale;
- классами для работы с файловой системой: QDir, QFileInfo;
- классами для работы со шрифтами: QFont, QFontInfo, QFontMetrics, QFontMetricsF;
- классами для работы с SQL: QSqlField, QSqlQuery, QSqlRecord;
- различными классами QtGui: QBitmap, QBrush, QCursor, QGLColormap, QGradient, QIcon, QImage, QKeySequence, QPainterPath, QPalette, QPen, QPicture, QPixmap, QRegion, QTextBoundaryFinder, QTextCursor, QTextDocumentFragment, QTextFormat, QX11Info.
Неявно разделяемые данные могут копироваться между потоками.
6.2. Создание собственных классов с разделением данных
Для создания собственных классов с разделением данных используется QSharedData. Пусть нам нужно создать класс Book, представляющий информацию о книге, чтобы разделялись данные об авторе, заглавии, годе издания и номере ISBN. Создадим вспомогательный класс BookData с соответствующими полями, наследующий от QSharedData (листинг 2.1).
Листинг 2.1. Класс BookData, хранящий разделяемые данные для Book
#include <QSharedData> #include <QString> class BookData : public QSharedData { public: BookData() : year(0) { author.clear(); title.clear(); isbn.clear(); } BookData (const BookData& other) : QSharedData(other), author(other.author), title(other.title), year(other.year), isbn(other.isbn) {} ~BookData() {} QString author; QString title; ushort year; QString isbn; }; |
Далее мы можем использовать указатель на данные QSharedDataPointer<BookData> (листинг 2.2).
Листинг 2.2. Класс Book с разделяемыми данными внутри BookData
#include <QString> #include <QSharedDataPointer> #include "bookdata.h" class Book { public: Book() { d = new BookData; } Book (QString author, QString title, ushort year, QString isbn) { d = new BookData; setAuthor (author); setTitle (title); setYear (year); setIsbn (isbn); } Book (const Book& other) : d (other.d) {} QString author() const { return d->author; } void setAuthor(QString author) { d->author = author; } QString title() const { return d->title; } void setTitle(QString title) { d->title = title; } ushort year() const { return d->year; } void setYear(ushort year) { d->year = year; } QString isbn() const { return d->isbn; } void setIsbn(QString isbn) { d->isbn = isbn; } private: QSharedDataPointer<BookData> d; }; |
BookData скрывается от пользователя, и в программный интерфейс входит только класс Book.
Разделение данных будет работать следующим образом (листинг 2.3).
Листинг 2.3. Пример работы с Book при неявном разделении данных
Book cpppl97 ("Bjarne Stroustrup", "The C++ Programming Language", 1997, "0201889544"); Book cpppl00; cpppl00 = cpppl97; // неглубокая копия cpppl00.setYear(2000); // теперь требуется глубокая копия cpppl00.setIsbn("0201700735"); qDebug() << cpppl97.isbn(); // "0201889544" qDebug() << cpppl00.isbn(); // "0201700735" |
Если требуется явное разделение памяти, то QSharedDataPointer<BookData> просто заменяется на QExplicitlySharedDataPointer<BookData>. Получаем другую логику работы (листинг 2.4).
Листинг 2.4. Пример работы с Book при явном разделении данных
cpppl00 = cpppl97; // изменения затронут и данные cpppl97 cpppl00.setYear(2000); cpppl00.setIsbn("0201700735"); qDebug() << cpppl97.isbn(); // "0201700735" qDebug() << cpppl00.isbn(); // "0201700735" |
QSharedData добавляет к данным счетчик ссылок (с атомарными операциями, чтобы обеспечить безопасную многопоточность).
QSharedDataPointer<T> предоставляет метод detach(), который создает глубокую копию данных, если значение счетчика ссылок больше 1.
Оператор -> перегружен таким образом, что возвращает указатель на данные. Если требуется T*, то вызывается detach(); если требуется const T*, то detach() не вызывается:
T* QSharedDataPointer::operator-> (); const T* QSharedDataPointer::operator-> () const; |
То же самое делает метод data(), возвращающий T* или const T* (с detach() или без); constData() возвращает const T*.
Аналогично перегружен оператор разыменования (унарная *) и приведение к указателю на данные. Есть const-версия без вызова detach() и не-const-версия с вызовом detach(). QSharedDataPointer<T> работает как обычный указатель T*, и помимо разыменования позволяет сравнение через == и !=.
Конструктор копирования и оператор присваивания увеличивают счетчик ссылок для новых данных. В ходе присваивания и уничтожения объекта счетчик ссылок у старых данных уменьшается.
QExplicitlySharedDataPointer<T> тоже предоставляет метод detach(), но не вызывает его при передаче не-const данных (в отличие от QSharedDataPointer<T>). При необходимости detach() вызывается вручную.
Если в QExplicitlySharedDataPointer<T> часто возникает необходимость вызова detach(), то лучше использовать вместо него QsharedDataPointer<T>.
Таким образом, класс работает как обычный указатель с подсчетом ссылок, удаляющий данные только в том случае, если на счетчике остается 0.
Заключение
При написании переносимого кода удобно использовать специальные typedef, такие как quint32 или quint64. Иногда typedef вводится просто для краткой записи, например ulong как синоним unsigned long.
Для эффективной работы с пользовательскими типами необходимо знать, являются ли они примитивами без конструктора и деструктора либо сложными типами. Не стоит также забывать, можно ли перемещать объекты в памяти. В Qt это указывается через макрос Q_DECLARE_TYPEINFO.
В качестве объектов, которые могут хранить значения разных типов, используются варианты QVariant. Они похожи на объединения, но работают удобнее и безопаснее.
Для удобства разработчика предусмотрены «умные» указатели, которые работают как обычные, но предоставляют дополнительные возможности. Защищенные указатели QPointer<T> обнуляются при удалении объекта, QSharedPointer<T> работает как жесткая ссылка, а QWeakPointer<T> – как слабая.
Многие классы Qt неявно разделяют данные. Новые классы с неявным или явным разделением данных легко реализуются через QSharedData и QSharedDataPointer<T> или QExplicitlySharedDataPointer<T>.
Подсчет ссылок везде осуществляется с атомарными операциями над счетчиком, поэтому безопасен для многопоточных приложений.
В следующей статье мы продолжим разговор о базовых сведениях по программированию с использованием Qt и рассмотрим контейнеры, а также алгоритмы.
![]() |
|
Загрузка
Скачать исходный код с примерами (zip, 3 кб)
Ресурсы
-
Примите участие в обсуждении материала на форуме IBM developerWorks.
-
Исходный код с примерами (EN).
-
Qt Software (EN).
-
Документация по Qt (EN).
-
Qt Centre (EN).
-
Информационный бюллетень «Qt Quarterly» (EN).
-
Списки рассылки Qt (EN).
-
Jasmin Blanchette, Mark Summerfield. C++ GUI Programming with Qt 4 (2nd Edition) (EN). Prentice Hall: 2006. – 752 pp.
-
Daniel Molkentin. The Book of Qt 4: The Art of Building Qt Applications (EN). No Starch Press: 2007. – 440 pp.
-
J. Thelin. Foundations of Qt Development (EN). Apress: 2007. – 528 pp.
-
Bjarne Stroustrup. The C++ Programming Language (Special Edition) (EN). Addison-Wesley: 2000 – 1040 pp.
-
Ж. Бланшет, М. Саммерфилд. Qt 4. Программирование GUI на C++. 2-е издание. Кудиц-пресс: 2008 – 736 с.
- Б. Страуструп. Язык программирования C++. 2-е издание. Бином: 2007 – 1099 с.
Об авторе
![]() |
||
![]() |
Алексей Бешенов --- независимый разработчик и технический писатель, работающий со свободным программным обеспечением и свободными технологиями. Интересуется функциональным и логическим программированием, занимается математикой и теоретической информатикой. |