Правильное хранение и управление временем жизни динамически создаваемых объектов не так просто в C ++. После долгого времени борьбы я начал отдавать предпочтение некоторым методам, которые я до сих пор использую довольно успешно:
- локальные переменные (хранение и время жизни, управляемые областями)
- переменные-члены без указателей
- интеллектуальные указатели с четким владением (общий указатель) или без владения (слабый указатель).
При этом я почти полностью запретил использование исходных указателей в моих источниках, что привело к значительному снижению затрат на обслуживание и уменьшению раздражающей отладки.
Конечно, когда вступают в игру внешние библиотеки (например, наборы виджетов), я привязан к их API.
Что касается gtkmm 2.4, вышеупомянутые методы также работали довольно хорошо. GTKMM обеспечивает
- умные указатели для разделяемых объектов
- какое-то владение виджетами, относящимися к дочерним виджетам.
Когда я переключился на Qt, я увидел все new
s и необработанные указатели в примерах учебника, которые немного меня испугали. После некоторых экспериментов я пришел к выводу, что смогу писать полнофункциональные приложения на Qt, аналогичные тем, которые я делал раньше в gtkmm & ndash; почти без необходимости new
путем (снова) определения виджетов в качестве локальных переменных (например, в main()
) или переменных-членов других классов, полученных (прямо или косвенно) из QWidget
.
Кроме того, со временем я осознал общую концепцию Qt Деревья объектов и владение , но должен признать, что я редко полагаюсь на это в повседневной работе.
Относительно конкретной проблемы ОП:
A QWidget
является производным от QObject
. Следовательно, применяется обычный принцип владения QObject
s. Кроме того, QWidget
ожидает другого QWidget
в качестве родителя. QWidget
может быть родителем любого QObject
s, но не наоборот.
Следовательно, я бы предложил следующее право собственности:
MainWindow
является родителем DataReadController
MainWindow
является родителем ErrorMsgDialog
(создается в DataReadController
).
DataReadController
сохраняет указатель на ErrorMsgDialog
как необработанный указатель. (Я полагаю, что право собственности, предоставленное QSharedPointer
, вступит в конфликт с владением MainWindow
.)
(Как уже было описано выше, в моем собственном программировании я бы попытался вообще запретить указатели и использовать (не указатели) переменные-члены. Однако, IMHO, это вопрос стиля и личных предпочтений.)
Модифицированный образец ОП testQParentship.cc
:
#include <QtWidgets>
class Label: public QLabel {
private:
const QString _name;
public:
Label(const QString &name, const QString &text):
QLabel(text),
_name(name)
{ }
virtual ~Label()
{
qDebug() << _name + ".~Label()";
}
};
class HBoxLayout: public QHBoxLayout {
private:
const QString _name;
public:
HBoxLayout(const QString &name):
QHBoxLayout(),
_name(name)
{ }
virtual ~HBoxLayout()
{
qDebug() << _name + ".~HBoxLayout()";
}
};
class ErrorMsgDlg: public QDialog {
private:
const QString _name;
public:
ErrorMsgDlg(const QString &name, QWidget *pQParent):
QDialog(pQParent),
_name(name)
{
QHBoxLayout *pQBox = new HBoxLayout("HBoxLayout");
pQBox->addWidget(
new Label("Label", "An unknown read error occured!"));
setLayout(pQBox);
setWindowTitle("Error!");
}
virtual ~ErrorMsgDlg()
{
qDebug() << _name + ".~ErrorMsgDlg()";
}
};
class DataReadCtrl: public QObject {
private:
const QString _name;
ErrorMsgDlg *const _pDlgErrorMsg;
public:
DataReadCtrl(const QString &name, QWidget *pQParent):
QObject(pQParent),
_name(name),
_pDlgErrorMsg(
new ErrorMsgDlg(name + "._pDlgErrorMsg", pQParent))
{
//simulate that DataReader emits an error msg
QTimer::singleShot(2000, [&]() {
onErrorTriggered();
});
}
virtual ~DataReadCtrl()
{
qDebug() << _name + ".~DataReadCtrl()";
}
void onErrorTriggered()
{
_pDlgErrorMsg->show();
}
};
class MainWindow: public QMainWindow {
private:
const QString _name;
DataReadCtrl *_pCtrlReadData;
public:
MainWindow(const char *name):
QMainWindow(),
_name(name),
_pCtrlReadData(nullptr)
{
_pCtrlReadData
= new DataReadCtrl(_name + "._pCtrlReadData", this);
//Close after 5 seconds.
QTimer::singleShot(5000, [&]() {
qDebug() << _name + ".close()";
close();
});
}
virtual ~MainWindow()
{
qDebug() << _name + ".~MainWindow()";
}
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
MainWindow winMain("winMain");
winMain.show();
// runtime loop
return app.exec();
}
и минимальный файл проекта Qt testQParentship.pro
:
SOURCES = testQParentship.cc
QT += widgets
Скомпилировано и протестировано в cygwin64 в Windows 10:
$ qmake-qt5 testQParentship.pro
$ make && ./testQParentship
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQParentship.o testQParentship.cc
g++ -o testQParentship.exe testQParentship.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4
После истечения срока действия QTimer::singleshot()
in MainWindow
главное окно закрывается. Результаты диагностики показывают, что дерево объектов разрушено должным образом (вместо того, чтобы «выбрасывать», когда ОС освобождает память процесса).
"winMain.close()"
"winMain.~MainWindow()"
"winMain._pCtrlReadData.~DataReadCtrl()"
"winMain._pCtrlReadData._pDlgErrorMsg.~ErrorMsgDlg()"
"HBoxLayout.~HBoxLayout()"
"Label.~Label()"