MyTetra Share
Делитесь знаниями!
Как в Qt устроены умные указатели QPointer и QSharedPointer и как с ними работать
Время создания: 06.09.2024 11:19
Текстовые метки: qt, qt4, qt5, qt6, умный указатель, QPointer, QSharedPointer
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Библиотека Qt - Принципы написания кода
Запись: xintrea/mytetra_syncro/master/base/172561078176a6t6j4rl/text.html на raw.github.com

Здесь приведен результат объяснений, которые дала небезызвестная нервная сетка по поводу того, как устроены и как работают умные указатели в Qt. Вопросы задавались по устройству QPointer и QSharedPointer. Получилась неплохая выжимка из книг/документации с объяснением сложных моментов.


* * *



Вопрос


Объясни, как внутри устроены умные указатели QSharedPointer. Когда в программе существует несколько QSharedPointer на один и тот же объект, каким образом контролируется количество этих указателей на данный объект? Существует какой-то менеджер указателей, или все реализуется только через тот факт, что у QSharedPointer контролируемое присвоение объектов указателей?



Ответ


Внутреннее устройство QSharedPointer в Qt реализовано таким образом, что за счет дополнительного объекта, называемого контролируемым блоком (control block), отслеживается количество активных ссылок на управляемый объект. Этот механизм гарантирует, что объект будет автоматически удален, как только на него больше не будет ссылок.

Основные компоненты QSharedPointer:

  1. Управляемый объект — это объект, на который указывает QSharedPointer. Он управляется автоматическим счетчиком ссылок, и его удаление происходит автоматически, когда больше нет активных указателей на этот объект.
  2. Контролируемый блок (control block) — это структура, которая содержит счетчик ссылок и, возможно, другую служебную информацию. Именно в этом блоке хранится информация о том, сколько указателей ссылаются на управляемый объект.

Как работает счетчик ссылок:

  • Каждый раз, когда создается новый объект QSharedPointer, указывающий на тот же управляемый объект, увеличивается счетчик ссылок в контролируемом блоке.
  • Когда указатель перестает указывать на объект (например, при уничтожении или присвоении нового значения), счетчик уменьшается.
  • Когда счетчик ссылок достигает нуля, то контролируемый объект удаляется.

Контролируемый блок создается при первом QSharedPointer:

  • При первом создании QSharedPointer, управляющего каким-либо объектом, создается также и контролируемый блок, в котором инициализируется счетчик ссылок, равный 1.
  • При копировании QSharedPointer (или использовании присваивания) счетчик увеличивается на 1.
  • При уничтожении указателя (или присвоении другого объекта) счетчик уменьшается на 1.

Внутренняя реализация:

  • Когда вы создаете новый QSharedPointer, например, так:

  • QSharedPointer<MyClass> ptr1 = QSharedPointer<MyClass>::create();


    создается как управляемый объект MyClass, так и контролируемый блок с инициализацией счетчика ссылок равной 1.

  • Когда вы копируете указатель:

  • QSharedPointer<MyClass> ptr2 = ptr1;


    оба указателя (ptr1 и ptr2) теперь ссылаются на один и тот же объект, а счетчик ссылок увеличивается до 2.

  • Когда один из указателей уничтожается (например, выходит из области видимости), счетчик ссылок уменьшается:


ptr1.clear(); // Счетчик ссылок уменьшился на 1


Если в этот момент счетчик ссылок становится равным 0, управляемый объект удаляется.

Нет внешнего менеджера:

Все управление ссылками реализуется внутри самих объектов QSharedPointer и контролируемого блока. Нет какого-то глобального менеджера указателей или внешнего механизма отслеживания ссылок — вся логика встроена в сам QSharedPointer и его контролируемый блок.

Пример:


QSharedPointer<MyClass> ptr1(new MyClass()); // Создаем объект и указатель

{

QSharedPointer<MyClass> ptr2 = ptr1; // Счетчик ссылок увеличился до 2

} // ptr2 уничтожен, счетчик ссылок снова 1

// После выхода из области видимости ptr1 объект будет удален автоматически


В этом примере счетчик ссылок увеличивается при создании второго указателя ptr2 и уменьшается при его удалении. Когда счетчик достигнет нуля, объект MyClass будет удален автоматически.

Заключение:

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


* * *


Вопрос


Стоит ли в большинстве ситуаций использовать QSharedPointer вместо QPointer? Ведь в больших программах могут происходить крупные изменениия, из-за которых на один объект вначале указывает один указатель, а потом, при изменениях в коде, на него начинают указывать несколько указателей. И если программист забудет аккукратно обойтись с QPointer, то объект может быть удален раньше, чем его перестанут использовать другие участки кода с другими QPointer на тот же объект.



Ответ


Выбор между QSharedPointer и QPointer в Qt зависит от ситуации и характера управления объектами. Давайте рассмотрим различия между ними и подход к выбору.

Различия между QSharedPointer и QPointer:

  1. QSharedPointer:
    • Умный указатель с счетчиком ссылок, который контролирует время жизни объекта.
    • Когда несколько частей кода ссылаются на один и тот же объект через QSharedPointer, объект будет удален только тогда, когда все указатели, ссылающиеся на объект, будут уничтожены или обнулены.
    • Подходит для ситуаций, когда важно автоматически управлять временем жизни объекта, гарантируя, что объект не будет удален, пока на него есть ссылки.
  2. QPointer:
  • Умный указатель, который не управляет временем жизни объекта, но следит за тем, чтобы указатель обнулялся, если объект удален.
  • Если объект удален где-то в коде вручную (например, через вызов delete), все QPointer, указывающие на этот объект, автоматически станут nullptr.
  • Подходит для ситуаций, когда объект создается и управляется в другом месте, а указатель нужен только для того, чтобы проверить, существует ли объект или он был удален.

Проблема с QPointer:

QPointer полезен в ситуациях, когда объект управляется вручную, и вы хотите только отслеживать его состояние, но он не контролирует время жизни объекта. Это означает, что если вы неправильно распорядитесь временем жизни объекта (например, удалите объект, когда на него еще есть активные ссылки), программа может обратиться к удаленному объекту через QPointer или другие указатели, что приведет к ошибкам.

QSharedPointer в большинстве ситуаций:

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

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

Аргументы в пользу использования QSharedPointer:

  1. Безопасность: QSharedPointer гарантирует, что объект будет удален только тогда, когда больше не останется активных указателей на него. Это исключает возможность того, что объект будет случайно удален раньше времени, как это может случиться с QPointer, если объект удаляется вручную.
  2. Простота использования: В сложных системах, где управление объектами может стать запутанным, QSharedPointer снимает необходимость отслеживать и вручную управлять временем жизни объекта. Это упрощает код и снижает вероятность ошибок.
  3. Гибкость: Если объект вначале используется одним компонентом (как при использовании обычного указателя или QPointer), но затем его начинают использовать и другие части программы, вы легко можете добавить новые QSharedPointer на этот объект без изменения принципов управления его временем жизни.

Когда использовать QPointer:

  • Если объект управляется извне (например, создается и удаляется в другом классе или системе), а вам нужно просто проверять, существует ли объект, QPointer будет достаточно.
  • Если время жизни объекта хорошо контролируется в одном месте, и вы не хотите нагружать систему лишним счетчиком ссылок, QPointer может быть уместным.

Заключение:

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


* * *


Вопрос


Ты написла, что "Если объект удален где-то в коде вручную (например, через вызов delete), все QPointer, указывающие на этот объект, автоматически станут nullptr". Каким образом не свазанные друг с другом несколько QPointer, указывающие на один и тот же объект, поймут что объект был удален и примут значение nullptr?



Ответ


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

Вот как это происходит:

  1. Связь с системой сигналов/слотов: QObject, от которого унаследованы многие классы в Qt, содержит внутренний механизм для отслеживания удаления объектов. Каждый объект, унаследованный от QObject, имеет встроенную систему слотов и сигналов, и один из этих сигналов — это destroyed(). Когда объект удаляется (через вызов delete), этот сигнал автоматически генерируется.
  2. Обнуление QPointer: Когда объект удаляется, все QPointer, которые указывают на этот объект, получают уведомление через систему сигналов/слотов. Каждый QPointer регистрируется для получения сигнала об уничтожении объекта, и когда объект удаляется, все такие указатели автоматически обнуляются (становятся nullptr).

Таким образом, несколько QPointer, указывающих на один и тот же объект, не связаны между собой напрямую, но каждый из них "слушает" удаляемый объект через систему сигналов/слотов. Когда объект удаляется, каждый QPointer получает сигнал о его уничтожении и обнуляет свою ссылку.

Внутренний механизм:

  • Когда вы создаете QPointer, он регистрируется с объектом, на который указывает. Это регистрация в самом объекте QObject.
  • В момент удаления объекта QObject, этот объект отправляет сигнал всем зарегистрированным QPointer, уведомляя их, что объект уничтожен.
  • Каждый из таких QPointer обнуляется, не полагаясь на другие указатели.

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


* * *


Вопрос


Есть ли такой же механизм оповещения об уничтожении объекта в QSharedPointer (как и в QPointer)? Что произойдет, если на один и тот же объект будет создано два QSharedPointer, причем эти умные указатели будут созданы по отдельности, не используя присвоения одного к другому?



Ответ


Нет, у QSharedPointer нет такого же механизма оповещения об уничтожении объекта, как у QPointer. Важно различать, что QSharedPointer и QPointer решают разные задачи:

  • QPointer — это слабый указатель, который отслеживает объект, унаследованный от QObject, и автоматически обнуляется, когда этот объект удаляется где-то в коде вручную.
  • QSharedPointer — это умный указатель с подсчетом ссылок, который автоматически управляет временем жизни объекта, удаляя его, когда на него больше не указывает ни один QSharedPointer. Однако, QSharedPointer не следит за объектами, созданными и удаляемыми вручную через delete.

Что произойдет, если на один объект будет создано два независимых QSharedPointer:

Если на один и тот же объект будет создано два отдельных QSharedPointer без использования присвоения одного к другому, произойдут следующие проблемы:

  1. Создание двух независимых QSharedPointer: Когда вы создаете два независимых QSharedPointer на один и тот же объект (pObj=new YourClass), например, через два вызова QSharedPointer<YourClass> ptr1(pObj) и QSharedPointer<YourClass> ptr2(pObj), каждый указатель ведет свой собственный подсчет ссылок для объекта. Это означает, что оба указателя считают, что являются "владельцами" объекта, но на самом деле у них независимые системы контроля за временем жизни объекта.
  2. Двойное удаление объекта: Каждый из QSharedPointer считает, что он должен удалить объект, когда его счетчик ссылок дойдет до нуля. В результате, если оба указателя будут удалены, каждый вызовет delete для объекта, что приведет к двойному освобождению памяти. Это опасная ситуация, которая вызовет неопределенное поведение программы и может привести к аварийному завершению (crash).

Правильное использование QSharedPointer:

  1. Правильное копирование: Чтобы корректно работать с QSharedPointer, нужно использовать копирование одного умного указателя в другой:

  2. QSharedPointer<YourClass> ptr1 = QSharedPointer<YourClass>::create();

    QSharedPointer<YourClass> ptr2 = ptr1; // ptr2 теперь делит владение с ptr1


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

  3. Передача владения: Если нужно передать владение объектом между функциями или классами, нужно передавать QSharedPointer по значению или ссылке, а не создавать новые указатели на тот же объект с помощью new.

Вывод:

QSharedPointer не имеет механизма отслеживания "внешнего" удаления объекта, как это делает QPointer. Для правильного использования QSharedPointer важно не создавать несколько независимых указателей на один и тот же объект напрямую. Всегда используйте присвоение одного указателя другому, чтобы они корректно делили владение объектом и управляли его временем жизни.


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