Widgets handling without new (and without double-delete!)
-
Hi,
the main Qt idiom seems to be to use widgets on the heap via
new
.
But this is not strictly needed.Here's a nice example of handling things without new:
dialog.h
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QPushButton> #include <QVBoxLayout> #include <QTextStream> QTextStream qout{stdout, QIODevice::WriteOnly}; class MyPushButton : public QPushButton { public: MyPushButton( QWidget * parent = 0) : QPushButton{ parent} { ctorMsg(); } MyPushButton(const QString & text, QWidget * parent = 0) : QPushButton{text, parent} { ctorMsg(); } MyPushButton(const QIcon & icon, const QString & text, QWidget * parent = 0) : QPushButton(icon, text, parent) { ctorMsg(); } ~MyPushButton() { dtorMsg(); } private: void ctorMsg() { qout << "ctor MyPushButton (" << text() << ")" << endl; } void dtorMsg() { qout << "dtor MyPushButton (" << text() << ")" << endl; } }; class MyLayout : public QVBoxLayout { public: MyLayout(QWidget * parent = 0) : QVBoxLayout{ parent } { ctorMsg(); } ~MyLayout() { dtorMsg(); } private: void ctorMsg() { qout << "ctor MyLayout" << endl; } void dtorMsg() { qout << "dtor MyLayout" << endl; } }; class Dialog : public QDialog { public: Dialog(QWidget *parent = nullptr) : QDialog{ parent }, layout{ this }, oneButton{"One"}, twoButton{"Two"} { ctorMsg(); layout.addWidget(&oneButton); layout.addWidget(&twoButton); } ~Dialog() { dtorMsg(); } private: void ctorMsg() { qout << "ctor Dialog" << endl; } void dtorMsg() { qout << "dtor Dialog" << endl; } MyLayout layout; MyPushButton oneButton; MyPushButton twoButton; }; #endif
main.cpp
#include <QApplication> #include "dialog.h" int main(int argc, char *argv[]) { QApplication app{argc, argv}; Dialog dialog; dialog.show(); return app.exec(); }
project.pro
TEMPLATE = app TARGET = go CONFIG += c++11 QT += widgets INCLUDEPATH += . HEADERS += dialog.h SOURCES += main.cpp
The output is:
ctor MyLayout ctor MyPushButton (One) ctor MyPushButton (Two) ctor Dialog dtor Dialog dtor MyPushButton (Two) dtor MyPushButton (One) dtor MyLayout
You might ask:
Is this completely safe? Are there no double deletes?Answer: yes in this manner it is completely safe.
Even if I change the constructor-order, by changing (indialog.h
) toMyPushButton oneButton; MyLayout layout; /* now layout is inbetween ->what happens on delete? */ MyPushButton twoButton;
Then the output would be
ctor MyPushButton (One) ctor MyLayout ctor MyPushButton (Two) ctor Dialog dtor Dialog dtor MyPushButton (Two) dtor MyLayout dtor MyPushButton (One)
You may wonder: Does not
MyPushButton (One)
get double-deleted?
No. MyLayout is not a parent ofoneButton
. Instead the parent is Dialog - or rather Dialog's baseclass QWidget (because, we setlayout{ this }
, which is equivalent to callingsetLayout(layout)
).Here is the appropriate reference: http://doc.qt.io/qt-5/qwidget.html#setLayout
"The QWidget will take ownership of layout." and of it's added Widgets.But then if we look above, we see that
dtor Dialog
is called before other dtors!
Doesdtor Dialog
not delete the children?? (lurking double delete?)
No, not directly:Dialog
is derived from the baseclass QWidget.
And if we were to list the ctor and dtor of the baseclass as well, we get (via normal C++ rules):ctor QWidget ctor MyPushButton (One) ctor MyLayout ctor MyPushButton (Two) ctor Dialog dtor Dialog dtor MyPushButton (Two) dtor MyLayout dtor MyPushButton (One) dtor QWidget
So for children: Only
dtor QWidget
would delete children in the layout, if there are any left. But since these children's destructor was already called (normal stack... destructor), the children have already been removed from the parent QWidget.So all is good.
No double deletes.Right?
-
For double delete examples:
main.cpp
#include <QObject> #include <QTextStream> QTextStream qout{stdout, QIODevice::WriteOnly}; class MyClass : public QObject { public: MyClass(const QString& name, QObject * parent = nullptr) : QObject{ parent }, m_name{ name } { ctorMsg(); } ~MyClass() { dtorMsg(); } private: void ctorMsg() { qout << "ctor MyClass (" << m_name << ')' << endl; } void dtorMsg() { qout << "dtor MyClass (" << m_name << ')' << endl; } const QString m_name; }; int main() { MyClass child{"child"}; MyClass parent{"parent"}; child.setParent(&parent); return 0; }
outputs:
ctor MyClass (child) ctor MyClass (parent) dtor MyClass (parent) dtor MyClass (child) *** Error in `./go': free(): invalid size: 0x00007ffdae7af870 *** Aborted
To fix it:
changemain.cpp
to//... int main() { MyClass parent{ "parent" }; MyClass child{ "child", &parent }; return 0; }
Now the output is correct:
ctor MyClass (parent) ctor MyClass (child) dtor MyClass (child) dtor MyClass (parent)
PS: you need the following project file:
project.pro
TEMPLATE = app TARGET = go CONFIG += c++11 INCLUDEPATH += . HEADERS += SOURCES += main.cpp