Управління пам'яттю в Qt?


96

Я зовсім новачок у Qt, і мені цікаво про деякі основні речі з управління пам’яттю та життям об’єктів. Коли потрібно видаляти та / або знищувати свої об'єкти? Чи обробляється що-небудь із цього автоматично?

У прикладі нижче, який із створених мною об’єктів мені потрібно видалити? Що відбувається зі змінною екземпляра myOtherClassпри myClassзнищенні? Що станеться, якщо я взагалі не видалю (або не знищую) свої об’єкти? Це буде проблемою для пам'яті?

MyClass.h

class MyClass
{

public:
    MyClass();
    ~MyClass();
    MyOtherClass *myOtherClass;
};

MyClass.cpp

MyClass::MyClass() {
    myOtherClass = new MyOtherClass();

    MyOtherClass myOtherClass2;

    QString myString = "Hello";
}

Як бачите, це досить легкі речі для новачків, але де я можу про це дізнатися простим способом?

Відповіді:


100

Якщо ви будуєте власну ієрархію за допомогою QObjects, тобто ініціалізуєте всі новостворені QObjects батьком,

QObject* parent = new QObject();
QObject* child = new QObject(parent);

то досить , тому що s деструкція буде піклуватися про знищення . (Це робиться за допомогою видачі сигналів, тому це безпечно, навіть якщо ви видаляєте вручну перед батьком.)deleteparentparentchildchild

Ви також можете спочатку видалити дитину, порядок не має значення. Для прикладу, де порядок має значення, ось документація про дерева об’єктів .

Якщо ваша MyClassдитина не є дочірньою особою QObject, вам доведеться використовувати звичайний спосіб роботи на C ++.

Також зверніть увагу, що ієрархія батьків-дочірніх QObjects, як правило, не залежить від ієрархії дерева ієрархії / спадкування класу C ++. Це означає, що призначена дитина не повинна бути прямим підкласом батьків . Будь-якого (підкласу) QObjectбуде достатньо.

Однак можуть існувати деякі обмеження, накладені конструкторами з інших причин; наприклад, in QWidget(QWidget* parent=0), де батько повинен бути іншим QWidget, наприклад, завдяки прапорам видимості та тому, що ви зробили б такий базовий макет таким чином; але для ієрархічної системи Qt загалом вам дозволено мати будь-яку QObjectяк батьківську.


21
(It does this by issuing signals, so it is safe even when you delete child manually before the parent.)-> Це не причина, чому це безпечно. У Qt 4.7.4 діти QObject видаляються безпосередньо (через delete, див. Qobject.cpp, рядок 1955). Причиною того, чому безпечно спочатку видаляти дочірні об’єкти, є те, що QObject каже батькові забути його, коли його видаляють.
Martin Hennings

5
Я б додав, що ви повинні переконатися, що деструктори нащадків є віртуальними, щоб це було правдою. Якщо ClassBуспадковує від QObjectі ClassCуспадковує від ClassB, тоді ClassCналежним чином буде знищено відносинами батьків-дочірніх Qt, лише якщо ClassBдеструктор є віртуальним.
Phlucious

1
Посилання у відповіді зараз порушено (не дивно через майже 4 роки ...), можливо, це було щось на зразок цього qt-project.org/doc/qt-4.8/objecttrees.html ?
PeterSW

2
Деструктор @Phlucious QObject вже є віртуальним, що робить деструктор кожного підкласу віртуальним автоматично.
rubenvb

1
Якщо один клас десь у дереві успадкування має віртуальний деструктор, кожен дочірній клас нижче матиме віртуальний деструктор. Тепер, якщо поза таким віртуальним ланцюжком деструктора є листяний батьківський клас без віртуального деструктора, я вважаю, що у вас можуть виникнути проблеми, якщо ви видалите вказівник на цей конкретний клас, коли фактичний об’єкт знаходиться десь нижче цього ланцюга. У випадку дочірнього класу QObject та видалення вказівника QObject на екземпляр цього дочірнього класу ніколи не виникає проблем, навіть якщо ви забули віртуальне ключове слово в декларації деструктора цього підкласу.
rubenvb

47

Я хотів би розширити відповідь Дебільського, зазначивши, що поняття власності є дуже важливим у Qt. Коли клас A приймає право власності на клас B, клас B видаляється, коли клас A видаляється. Існує кілька ситуацій, коли один об’єкт стає власником іншого, а не лише тоді, коли ви створюєте об’єкт і вказуєте його батька.

Наприклад:

QVBoxLayout* layout = new QVBoxLayout;
QPushButton someButton = new QPushButton; // No owner specified.
layout->addWidget(someButton); // someButton still has no owner.
QWidget* widget = new QWidget;
widget->setLayout(layout); // someButton is "re-parented".
                           // widget now owns someButton.

Інший приклад:

QMainWindow* window = new QMainWindow;
QWidget* widget = new QWidget; //widget has no owner
window->setCentralWidget(widget); //widget is now owned by window.

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

Як заявив Дебільський, ці правила застосовуються ТІЛЬКИ до об'єктів, що походять від QObject. Якщо ваш клас не походить від QObject, вам доведеться впоратися із знищенням самостійно.


У чому різниця між написанням: QPushButton * someButton = new QPushButton (); або QPushButton someButton = новий QPushButton або просто QPushButton someButton;
Мартін

3
Ех, існує величезна різниця між QPushButton * someButton = new QPushButton; і QPushButton someButton ;. Перший розподілить об’єкт у купі, тоді як другий розподілить його у стеку. Немає різниці між QPushButton * someButton = new QPushButton (); і QPushButton someButton = new QPushButton ;, обидва вони будуть викликати конструктор об'єкта за замовчуванням.
Остін,

Я дуже новачок у цьому, так що вибачте за запитання, але яка різниця між "розподілити об'єкт у купі" та "розподілити його у стеку"? Коли слід використовувати купу, а коли стек? Дякую!
Мартін,

3
Вам потрібно прочитати про динамічні розподіли, обсяг об'єктів та RAII. У випадку простого C ++, ви повинні розподіляти об'єкти в стеку, коли це можливо, оскільки об'єкти автоматично руйнуються, коли вони вичерпуються за межею. Для учасників класу краще розподіляти об'єкти в купі через ефективність. І щоразу, коли ви хочете, щоб об'єкт "пережив" виконання функції / методу, вам слід розподілити об'єкт у купі. Знову ж таки, це дуже важливі теми, які вимагають певного читання.
Остін

@Austin Загальне твердження про те, що вам слід розподілити членів класу в купі для продуктивності, це бички. Це насправді залежить, і ви повинні віддавати перевагу змінним з автоматичною тривалістю зберігання, поки не виявите проблему з продуктивністю.
rubenvb

7

Батько (або об'єкт QObject, або його похідний клас) має список вказівників на свої дочірні елементи (QObject / його похідні). Батько буде видаляти всі об'єкти зі свого дочірнього списку, поки батько буде знищений. Ви можете використовувати цю властивість QObject, щоб змусити дочірні об'єкти автоматично видалятися, коли будь-який батько буде видалено. Співвідношення можна встановити, використовуючи наступний код

QObject* parent = new QObject();
QObject* child = new QObject(parent);
delete parent;//all the child objects will get deleted when parent is deleted, child object which are deleted before the parent object is removed from the parent's child list so those destructor will not get called once again.

Існують інші способи управління пам’яттю в Qt за допомогою смарт-покажчика. Наступна стаття описує різні розумні вказівники в Qt. https://blog.qt.digia.com/blog/2009/08/25/count-with-me-how-many-smart-pointer-classes-does-qt-have/


-2

Щоб додати до цих відповідей, для верифікації я б рекомендував вам використовувати Visual Leak Detetorбібліотеку для ваших проектів Visual c ++, включаючи проекти Qt, оскільки вона базується на c ++, ця бібліотека сумісна з new, delete, free and mallocтвердженнями, добре документована та проста у використанні. Не забувайте, що коли ви створюєте власний QDialogабо QWidgetуспадкований клас інтерфейсу, а потім створюєте новий об'єкт цього класу, не забудьте виконати setAttribute(Qt::WA_DeleteOnClose)функцію вашого об'єкта.

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