Чому в офіційних прикладах та підручниках Qt не використовуються розумні вказівники?


76

Чому в офіційних прикладах та навчальних посібниках про бібліотеку Qt ніколи не використовуються розумні вказівники? Я бачу тільки newі deleteдля створення і знищення віджетів.

Я шукав обгрунтування, але не зміг його знайти, і сам не бачу, крім випадків, коли це пов’язано з історичними причинами або зворотною сумісністю: не всі хочуть, щоб програма припиняла роботу, якщо конструктор віджетів вийшов з ладу, і обробляти його за допомогою try / catch блоки просто потворні (навіть якщо вони використовуються в кількох місцях). Той факт, що батьківські віджети можуть отримати право власності на дітей, також лише частково пояснює це, оскільки вам все одно доведеться використовувати deleteдля батьків на якомусь рівні.


9
Qt мав розумні вказівники набагато раніше сучасного c ++. І вони використовуються в деяких проектах. Ви не бачите в цих прикладах, оскільки QObject вирішує всі проблеми, пов’язані з дітьми / батьками, і там існує життєвий цикл. для отримання додаткової інформації про розумні вказівники Qt відвідайте це -> wiki.qt.io/Smart_Pointers
sanjay

2
"вам все одно доведеться використовувати delete для батьків на якомусь рівні" - не обов'язково, оскільки кореневий об'єкт зазвичай сидить, main()тому він буде автоматично зібраний.
dtech

Здається, QPointer можна використовувати прозоро в різних контекстах: тобто він поводиться як "вказівник на область дії", якщо віджет, на який посилається, НЕ був доданий до батьківського об'єкта до того, як QPointer вийде за межі області дії, але, з іншого боку, зберігає віджет life, якщо його було додано до батьків. перед тим, як QPointer виходить за рамки
Мартін

Це не схоже на те, що звичайні вказівники були застарілими чи щось подібне, вони такі ж корисні, як ніколи раніше. На відміну від того, що могли б сказати деякі люди, користуватися ними все одно цілком нормально, а у випадку Qt є багато запобіжників навколо "гострих кутів" простих покажчиків. Але так, давайте припинимо використовувати покажчики зараз лише тому, що C ++ нарешті вирішив включити розумні вказівники.
dtech

5
@ddriver варто зауважити, що порада експерта - це "ніколи не використовувати необроблені вказівники". Порада - «уникайте володіння необробленими вказівниками». Це важлива відмінність, яку, здається, ви не робите.
Тім Сегін

Відповіді:


67

Оскільки Qt покладається на модель батьків-нащадків для управління ресурсами Qobject. Він слідує складеному шаблону + ланцюжок відповідальності, який використовується від управління подіями до управління пам'яттю, малювання, обробки файлів тощо ...

Насправді, спроба використовувати QObject у спільному \ унікальному покажчику є надмірною розробкою (99% випадків).

  1. Ви повинні надати спеціальний видалювач, який буде викликати deleteLater
  2. Ваш qobject з батьками вже має посилання в батьківському об'єкті. Отже, ви знаєте, що об’єкт не витікає, поки існує батьківський. Коли вам потрібно від нього позбутися, ви можете зателефонувати deleteLaterбезпосередньо.
  3. Ваш QWidget без батьківського зв’язку вже має посилання в об’єкті Qapplication . Так само, як і пункт 2.

Тим не менш, ви все ще можете використовувати RAII з Qt. Наприклад, QPointer поводиться як слабке посилання на QObject. Я QPointer<QWidget>скоріше скористався QWidget*.

Примітка: щоб не звучало занадто фанбой, два слова: Qt + valgrind.


2
пункт 4. якщо батька буде видалено, тоді shared_ptr та unique_ptr матимуть звисаючий вказівник. Це вирішується за допомогою QPointer, який автоматично очищається QObject, на який вказано.
фрік-фрік

Для об’єктів, які не мають віджетів, у яких немає батьків, все-таки потрібно викликати, deleteLaterа не просто deleteвводити об’єкт? Я часто керую екземплярами класів, похідних від QObject, за допомогою розумних покажчиків, тому я вже шукав відповідь на це, щоб переконатися, що я не роблю чогось небезпечного, і, наскільки я можу сказати, документація не вказує, що це неправильно в deleteпокажчик QObject нормально (навіть якщо він знаходиться в власності батьківським об'єктом, оскільки деструктор видалить посилання батька).
Kyle Strand

2
документація застерігає вас від цього . безпосередній виклик видалення може спричинити збій через очікувані події \ підключення до черги до об'єкта (або його дочірніх елементів).
UmNyobe

@UmNyobe Це правда, якщо ви видалите a QObjectз іншого QThread. Деструктор QObjectвідключає всі сигнали до нього та від нього і автоматично видаляє очікування з його черги подій. Тому закликати deletea QObject- це нормально, якщо ви робите це з його потоку. З deleteLater()огляду на це, навряд чи ви можете зробити щось погане, за винятком випадків, коли вам потрібно негайно прибрати з якихось причин.
Ральф Тандецький,

@UmNyobe Згідно з документацією QPointer, це не має нічого спільного з RAII. Це просто гарантує, що покажчик буде 0, якщо хтось інший видалить об'єкт QO, на який він вказує. Але він ніколи не видаляє сам QObject. Якщо ви RAII, найкращим методом повинен бути QSharedPointer.
Фріц

26

Розумні вказівники для дітей

Смарт - класи покажчиків std::unique_ptrі std::shared_ptrпризначені для управління пам'яттю. Наявність такого розумного вказівника означає, що ви є власником вказівника. Однак при створенні QObjectабо похідному типі з QObjectбатьком право власності (відповідальність за прибирання) передається батькові QObject. У цьому випадку стандартні бібліотечні розумні вказівники непотрібні або навіть небезпечні, оскільки вони можуть призвести до подвійного видалення. Так!

Сирі вказівники на дітей-сиріт

Однак, коли QObject(або похідний тип) створюється в купі без батьківського файлу, QObjectречі дуже сильно відрізняються. У цьому випадку вам слід не просто тримати вказівник raw, але розумний вказівник, бажано a std::unique_ptrна об’єкт. Таким чином ви отримуєте безпеку ресурсів. Якщо згодом ви передасте право власності на об’єкт батькові, яким QObjectви можете скористатися std::unique_ptr<T>::release(), приблизно так:

auto obj = std::make_unique<MyObject>();
// ... do some stuff that might throw ...
QObject parentObject;
obj->setParent( &parentObject );
obj.release();

Якщо речі, які ви робите перед тим, як дати батькові-сироті батьків, видає виняток, тоді у вас буде витік пам'яті, якщо ви використовували необроблений вказівник для утримання об'єкта. Але наведений вище код заощаджує від такого витоку.

На більш загальній ноті

Це не сучасна порада на C ++ - уникати сирих покажчиків разом, а уникати володіння необробленими вказівниками. Я можу додати ще одну сучасну пораду на C ++: не використовуйте розумні вказівники на об'єкти, які належать якійсь іншій сутності програми.


4
Так. Це. Власність - це все. Я хотів би, щоб документація Qt була трохи більш чіткою щодо цього; здається, є багато посилань на те, що діти видаляються "автоматично", але не так багато інформації про те, як слід поводитись із сиротами.
Kyle Strand

+1, зокрема для останнього абзацу. Я бачив безліч випадків, коли люди невпинно намагаються використовувати розумні вказівники, де б вони не могли (зокрема щодо передачі параметрів, див. Також цю основну настанову.
andreee,

13

Ви вже відповіли на своє питання: except if it's for historic reasons/backward compatibility. Бібліотека настільки ж велика, як QT, не може припустити, що кожен, хто користується бібліотекою, має компілятори, що підтримують C ++ 11. newі deleteгарантовано існуватимуть у попередніх стандартах.

Однак, якщо у вас є підтримка використання розумних покажчиків, я рекомендую використовувати їх над необробленими вказівниками.


однак розумні вказівники існували до C ++ 11 і, можливо, до QT. Це було поганим дизайнерським рішенням з самого початку?
Мартін

21
помилка ... коли qt та деякі інші бібліотеки були розроблені \ реалізовані, c ++ навіть не мав стандартного класу рядків.
UmNyobe

Якщо розумні вказівники перед C ++ 11 означають auto_ptr, то правильним рішенням було їх не використовувати. До C ++ 11 не було хороших стандартизованих розумних покажчиків.
Zyx 2000,

2
Немає хороших стандартизованих, але деякі на основі Qt, наприклад QSharedPointerі QScopedPointer.
Руслан

Ця відповідь датована, оскільки сучасний Qt потребує підтримки C ++ 11.
Михайло

10

На додаток до того, що сказав @Jamey:

Якщо ви продумаєте його розумно, можливо, вам ніколи не доведеться використовувати видалення у віджеті. Скажімо, у вас є головне вікно, і ви створюєте його автоматичний об’єкт і запускаєте це вікно в циклі подій. Тепер решту всіх елементів цього віджета можна додати як його нащадки. І оскільки ви додаєте їх до цього головного вікна прямо / побічно як дитина, коли ви закриєте це головне вікно, про все буде потурбовано автоматично. Просто потрібно переконатися, що всі створені вами динамічні об’єкти / віджети є дітьми / онуками MainWindow. Отже, не потрібно явного видалення ..


2
Вам доведеться додати кожну дитину до головного вікна, як тільки ви його створите, або, принаймні, перед тим, що може викинути, або річ не працює, обмеження, про яке я хотів би уникати
Мартін

2
@Martin нічого не кидає в програми Qt зазвичай (якщо хтось не є справжнім шанувальником, тобто), так що давай :)
mlvljr

5
  • QObject має батьківський параметр, а деревоподібна структура програми дозволяє досить ефективно управляти пам'яттю.

  • Динамізм в Qt руйнує цей приємний ідеал, наприклад, передача необробленого вказівника. У кінцевому підсумку можна легко взяти dangling pointer, але це загальна проблема в програмуванні.

  • Розумний вказівник Qt, насправді слабкий посилання, є QPointer<T>
    і надає деякі цукерки STL.

  • Можна також змішувати std::unique_ptrі тому подібне, але його слід використовувати лише для машин, що не належать до Qt, у вашій програмі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.