MyTetra Share
Делитесь знаниями!
Умные указатели в Qt: статья на русском языке от IBM Developer
Время создания: 13.07.2020 09:25
Автор: IBM Developer
Текстовые метки: язык, c++, qt, qt4, qt5, умные указатели, QPointer, QScopedPointer, QSharedPointer, QWeakPointer
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Библиотека Qt - Принципы написания кода
Запись: xintrea/mytetra_syncro/master/base/1594621528v02ktkpkm3/text.html на raw.github.com

Защищенные указатели

Когда объект, на который ссылается указатель, разрушается, указатель становится «повисшим», т.е. содержащим адрес, по которому уже нет объекта. Это часто служит источником ошибок.

В 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 был удален

}


Разумеется, защищенные указатели предполагают дополнительные накладные расходы по сравнению с обычными. Оповещение об уничтожении объекта реализовано через подключение сигнала к слоту. Но обычно этим можно пренебречь.


Примечание: данный материал достаточно старый (2009 года), и не содержит описания указателя типа QScopedPointer. В чем отличие QScopedPointer от QPointer, надо разбираться отдельно. Объединяет эти умные указатели как минимум то, что в них отсутсвует подсчет ссылок.

На первый взгляд, и тот и другой умный указатель удаляют указываемый объект при выходе из области видимости, и присваивают указателю nullptr. Однако, у QPointer есть ограничение: он может ссылаться только на объекты, унаследованные от QObject.

Кроме того, QScopedPointer невозможно скопировать или присвоить: у него отсутсвует конструктор копирования и оператор =. А с QPointer данные действия не запрещены.

Подробнее о различиях в данных типах указателях можно прочитать в статье Размышления об умных указателях в Qt от Андрея Боровского. Отличия QPointer и QScopedPointer


Подсчет ссылок

Классы QSharedPointer и QWeakPointer реализуют подсчет ссылок.


Жесткие ссылки

Обычный указатель типа 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;



Слабые ссылки

Для слабых ссылок QWeakPointer<T> не задано разыменование. Они применяются только для того чтобы проверить, не был ли указатель удален. Аналогично, имеется метод isNull(), приведение к bool и оператор !. Слабые ссылки можно копировать, присваивать и сравнивать.

Допускается преобразование жесткой ссылки в слабую и наоборот:


QWeakPointer<T>   QSharedPointer::toWeakRef() const;

QSharedPointer<T> QWeakPointer::toStrongRef() const;


При этом, слабые ссылки создаются только из жестких, как в случае


QWeakPointer<Foo> wp(sp0);



Приведение

Для приведения типов предусмотрены вспомогательные функции. Если указатель на 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 не удается, то возвращается ссылка, соответствующая нулевому указателю.


Разделение данных

Объекты классов с разделяемыми данными без дополнительных накладок могут быть переданы по значению. При этом передаются только указатели на данные. Ведется подсчет ссылок, и данные удаляются, как только счетчик принимает значение 0.

При неявном разделении данных передаваемые указатели служат неглубокими копиями, а сами данные дублируются только при записи изменений (создаются глубокие копии). Присваивание создает неглубокую копию.

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

Иногда это называют семантикой значений и семантикой указателей.


Неявное разделение данных в 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.

Неявно разделяемые данные могут копироваться между потоками.


Создание собственных классов с разделением данных

Для создания собственных классов с разделением данных используется 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.


Заключение

Для удобства разработчика, в Qt предусмотрены «умные» указатели, которые работают как обычные, но предоставляют дополнительные возможности. Защищенные указатели QPointer<T> обнуляются при удалении объекта, QSharedPointer<T> работает как жесткая ссылка, а QWeakPointer<T> – как слабая.

Многие классы Qt неявно разделяют данные. Новые классы с неявным или явным разделением данных легко реализуются через QSharedData и QSharedDataPointer<T> или QExplicitlySharedDataPointer<T>.

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


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования