Как Qt удаляет объекты? И как лучше всего хранить QObjects?
Я слышал, что объекты в Qt автоматически удаляют своих детей, я хочу знать, что произойдет в этих ситуациях.
#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
/*
QLabel label("label"); // Program will crash. Destruct order is 1. widget, 2. layout, 3. label
QHBoxLayout layout; // But layout will be deleted twice
QWidget widget;
*/
QWidget widget; // Program doesn't seem to crash but is it safe ? Does Qt use
QHBoxLayout layout; // delete to operate on already destructed children ?
QLabel label("label");
layout.addWidget(&label); // layout is label parent
widget.setLayout(&layout); // widget is layout parent
widget.show();
return app.exec();
}
Разрешено ли это в Qt? Что делает Qt при уничтожении ребенка?
Кстати, я рассматривал использование интеллектуальных указателей, таких как shared_ptr. Но я думаю, что Qt также удалит объект, который уже был уничтожен интеллектуальным указателем тоже.
Я знаю, что вы хотели бы использовать new для распределения динамической памяти для объектов. Но я не чувствую его обнадеживающим, скажите, пожалуйста, если есть какие-либо ситуации (например, исключения), которые приведут к утечкам памяти, когда они будут полагаться на дерево объектов Qt для обработки динамической памяти?
Если я использую объекты, а не указатели, для динамического выделения объектов, я должен учитывать порядок уничтожения объектов, если они имеют право собственности, что является утомительным.
Я не знаю, хорошо ли использовать динамическую память в Qt.
Есть ли у вас предложения или лучшие решения?
Ответы
Ответ 1
Реализация Композитного шаблона дизайна QObject
была проверена и протестирована через множество версий Qt.
В шаблоне требуется, чтобы составной объект владел дочерними элементами, поэтому, пока родительский процесс был выполнен, вы можете быть уверены, что дочерний элемент QObjects
будет уничтожен при уничтожении родителя.
Стандартная практика заключается в создании дочерних объектов в кучевой памяти и немедленной родительской. Если вы не родитель немедленно, вы можете явно родительский, используя функцию setParent()
, иначе родительский процесс будет выполняться автоматически при добавлении виджета в родительский виджет, используя addWidget()
или addLayout()
.
QLayout
объекты - это менеджеры размера и компоновки других QLayouts
и QWidgets
. Они не владеют объектами, которыми они управляют. Родитель фактически является QWidget
, что QLayout
является дочерним.
У вас есть выбор для создания родительского корня в стеке или в памяти кучи.
Если вы чувствуете себя более комфортно с умными указателями, есть два класса, специально предназначенные для QObjects
: QPointer и QSharedPointer. У каждого есть свои плюсы и минусы.
#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWidget widget; // Root parent so can create as a auto-deleting object on the stack
QHBoxLayout *layout = new QHBoxLayout(&widget); // Create on the heap and parent immediately
QLabel *label = new QLabel("label", &widget); // Create on the heap and parent immediately
layout->addWidget(label); // widget remains label parent
widget.setLayout(layout); // widget is changed to layout parent if necessary, as well
// as any widgets that layout manages
widget.show();
return app.exec();
// layout and label are destroyed when widget is destroyed
}
Ответ 2
Добавив ответ RobbiE, QPointer и QSharedPointer - это два класса, которые выполняют разные функции.
QPointer и его оговорки
A QPointer
является слабым указателем на a QObject
. Он сбрасывается до нуля, когда объект, на который указывает объект, уничтожается. Это не владеющий указатель: он никогда не удаляет сам объект и не гарантирует существование объекта. Используйте его, чтобы избежать обвинчивания указателя на объект, владение которым управляется в другом месте. Проверьте, не указана ли указатель перед каждым использованием. Вы столкнетесь с условиями гонки, если объект будет уничтожен в другом потоке:
if (pointer) /* another thread can destruct it here */ pointer->method();
Сам QPointer
является потокобезопасным, но используемый код не может быть потокобезопасным из-за недостаточного API, предоставляемого QPointer
.
QPointer
всегда безопасен для использования из основного потока с объектами виджетов и с объектами, принадлежащими объектам виджетов, где установлены отношения родитель-ребенок. Объекты и их пользователи находятся в одном потоке, поэтому объект не будет удален другим потоком между нулевой проверкой указателя и использованием указателя:
QPointer<QLabel> label(...);
if (label) label->setText("I'm alive!");
Вам нужно быть осторожным, если вы повторно входите в цикл событий. Предположим, что:
QPointer<QTcpSocket> socket(...);
...
if (socket) {
socket->write(...);
socket->waitForBytesWritten();
// Here the event loop is reentered, and essentially any other code in your
// application can run, including code that could destruct the socket that
// you're using. The line below can thus dereference a null pointer
// (IOW: crash). Even worse, in such a case after the event loop returns
// back to `waitForBytesWritten`, `this` is a dangling pointer. So, before
// the line below crashes, something in `waitForBytesWritten` may do silly
// things, like formatting your hard drive.
socket->write(...);
}
По крайней мере, вам нужно повторно проверить QPointer
каждый раз после того, как вы вернетесь
от блокировки, повторного вызова цикла-события, такого как waitForXxx
или exec
. Именно поэтому блокирование вызовов является злым: вы никогда не должны их использовать.
QPointer<QTcpSocket> socket(...);
...
if (socket) {
socket->write(...);
socket->waitForBytesWritten();
// Reenters the event loop, the socket may get deleted.
}
// Not re-checking the pointer here would be a bug.
if (socket) {
socket->write(...);
...
}
QSharedPointer и QWeakPointer
A QSharedPointer
является владеющим указателем. Он работает как переменные в Java и Python, или как std::shared_ptr
. Пока существует хотя бы один QSharedPointer
, указывающий на объект, объект хранится вокруг. Когда уничтожается последний QSharedPointer
, объект разрушается и удаляется.
QWeakPointer
- кузена QSharedPointer
. Он не владеет. Он отслеживает, остаются ли объекты, хранящиеся в QSharedPointer
. Он сбрасывается до nullptr, когда последний QSharedPointer
, который владеет объектом, уходит. Его можно рассматривать как обобщение классов QPointer
для не QObject
. Единственный безопасный способ использования QWeakPointer
- преобразовать его в QSharedPointer
. Когда вы держите общий указатель, объект будет оставаться в живых.
A QPointer
как a QWeakPointer
для QObject
s, но для него не требуется существование a QSharedPointer
.
Ошибка использования QSharedPointer
для объекта, который не выделен в куче, а также для объекта, время жизни которого управляется другими механизмами. Например, это ошибка, чтобы иметь QSharedPointer
в QObject
, у которого есть родитель. Родитель объекта удалит его, и в итоге вы получите оборванную QSharedPointer
!
QScopedPointer
QScopedPointer
, как и std::unique_ptr
, является единственным владеющим указателем. Его задача - удалить удерживаемый объект, когда он выходит за рамки. Имя С++ 11 unique_ptr
очень подходит: это уникальный указатель, в том смысле, что это ошибка, чтобы попытаться скопировать такие указатели. Всегда существует только один QScopedPointer
, которому принадлежит данный объект, и он не взаимодействует с другими типами указателей. Вы можете получить указатель на базовый объект, вызвав метод data
.
станд:: auto_ptr
Из-за своей сломанной семантики копирования использование этого класса должно рассматриваться как ошибка. Используйте std::unique_ptr
или QScopedPointer
.