|
|||||||
Умные указатели в 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 данные неявно разделяются:
Неявно разделяемые данные могут копироваться между потоками. Создание собственных классов с разделением данныхДля создания собственных классов с разделением данных используется 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>. Подсчет ссылок везде осуществляется с атомарными операциями над счетчиком, поэтому безопасен для многопоточных приложений. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|