Программирование с Qt: Часть 2. Типы, варианты, ссылки и разделение данных

Qt – кроссплатформенный инструментарий для разработки прикладного программного обеспечения, широко используемый при создании графических пользовательских интерфейсов. В первом материале цикла мы рассмотрели основные инструменты разработчика, а также используемую в Qt объектную модель. Теперь перейдем к вопросам, касающиеся типов, вариантов, подсчета ссылок и разделения данных, поскольку без этих базовых сведений работать с инструментарием невозможно.

Алексей Бешенов, технический писатель, независимый специалист

Алексей Бешенов --- независимый разработчик и технический писатель, работающий со свободным программным обеспечением и свободными технологиями. Интересуется функциональным и логическим программированием, занимается математикой и теоретической информатикой.



08.10.2009

1. typedef в Qt

В Qt имеются директивы typedef, облегчающие кроссплатформенную разработку и написание имен типов (таблица 1).

Таблица 1. typedef в Qt

Для переносимости кода
qint88-битное знаковое целое
quint88-битное беззнаковое целое
qint1616-битное знаковое целое
quint1616-битное беззнаковое целое
qint3232-битное знаковое целое
quint3232-битное беззнаковое целое
qint6464-битное знаковое целое
quint6464-битное беззнаковое целое
quintptrБеззнаковый указатель (32 или 64 бита)
qptrdiffЗнаковый указатель (32 или 64 бита)
qrealВезде double; float только для архитектуры ARM
Для краткой записи типов
ucharunsigned char
uintunsigned int
ulongunsigned long
ushortunsigned short
qlonglonglong long int или __int64
qulonglongunsigned 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

ТипАвтоматически преобразуется в
BoolChar, Double, Int, LongLong, String, UInt, ULongLong
ByteArrayDouble, Int, LongLong, String, UInt, ULongLong
CharBool, Int, UInt, LongLong, ULongLong
ColorString
DateDateTime, String
DateTimeDate, String, Time
DoubleBool, Int, LongLong, String, UInt, ULongLong
FontString
IntBool, Char, Double, LongLong, String, UInt, ULongLong
KeySequenceInt, String
ListStringList
LongLongBool, ByteArray, Char, Double, Int, String, UInt, ULongLong
PointPointF
RectRectF
StringBool, ByteArray, Char, Color, Date, DateTime, Double, Font, Int, KeySequence, LongLong, StringList, Time, UInt, ULongLong
StringListList, String
TimeString
UIntBool, Char, Double, Int, LongLong, String, ULongLong
ULongLongBool, 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 и рассмотрим контейнеры, а также алгоритмы.


Загрузка

ОписаниеИмяРазмер
Исходный код с примерамиqt-examples-2.tar.gz10KB

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • Библиотека документов

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=433671
ArticleTitle=Программирование с Qt: Часть 2. Типы, варианты, ссылки и разделение данных
publish-date=10082009