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 (in dialog.h) to

      MyPushButton 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 of oneButton. Instead the parent is Dialog - or rather Dialog's baseclass QWidget (because, we set layout{ this }, which is equivalent to calling setLayout(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!
    Does dtor 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:
    change main.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
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.