Qt parent mechanism

前端 未结 3 866
悲&欢浪女
悲&欢浪女 2021-01-26 10:00

There is one QPushButton in a QWidget, click the button should open another QWidget, as coded below:

project.pro

3条回答
  •  -上瘾入骨i
    2021-01-26 10:19

    In addition to the problems with your destructor and constructor (and the memory leak you have because of the second), and the project file here are a few things that you may want to know in order to understand the whole parent situation:

    You don't need to pass this. The purpose of assigning a parent is to be able to simplify the cleanup procedure for QObject instances (including all classes inheriting from the QObject class). Qt parent-child model follows the C++ standard that is destructors are invoked in the reversed order of their constructors. In simple terms this means the following:

    Imagine you create widget A, B and C. Using the parent property you assign B and C to be children of A. So here we have the order of creation:

    1. A (parent)
    2. B,C (children)

    Now at some point you want to destroy your widgets (example: closing the application). The standard C++ way is to destroy them in the following (reversed to the construction) order:

    1. B, C (children)
    2. A (parent)

    And this is indeed what happens if we go for the parent first. However we are dealing with Qt here so we have to consider one additional feature the library provides - slots and signals.

    Whenever a QObject gets destroyed it emits a signal. Based on the role the object plays in the parent-child relationship the outcome is one of the following:

    • only the QObject that is destroyed gets destroyed - this is what happens when a child gets destroyed before we destroy its parent
    • all children of that QObject get destroyed first followed by the destruction of the QObject itself - this is what happens when a parent gets destroyed. The emitted signal is caught by all its children which in return also get eliminated.

    However assigning a parent is not mandatory. You can do the cleanup manually yourself. Read more about the parent-child model in the Qt documentation.

    In addition to all that ownership transfer (a widget becomes a child of another widget) often happens automatically so it is not necessary to explicitly specify a widget's parent. Again taking things out of the Qt documentation an exception is made here. If you add a widget to a layout, the ownership is NOT transferred to the layout itself but to the QWidget it is part of.

    Finally there is one important case when not assigning a parent makes things very, very different. Here I shall quote the Qt documentation on QObject:

    Setting parent to 0 constructs an object with no parent. If the object is a widget, it will become a top-level window.

    So if you have a QWidget and don't add it to some layout (and indirectly to the QWidget that has that layout) for example it will automatically become a separate window.


    EDIT: Check your constructor and in particular the way you work with your secondWidget object. As I've mentioned above in case you don't want to assign it to a parent widget you need to take care of the cleaning.

    You dynamically allocate the memory for it

        SecondWidget *secondWidget = new SecondWidget();
    

    and even connect it to your button

        connect(bt, &QPushButton::clicked, secondWidget, &SecondWidget::show);
    

    however you never release the allocated memory using delete or assigning it to a parent widget, which will take care of it automatically. Hence you create a memory leak. Connecting a QObject via signals and slots doesn't have anything to do with transfer of ownership.

    I personally assume that you actually want a secondWidget to be an extra window shown on the screen. In this case you need to create a class member of type SecondWidget

    SecondWidget *secondWidget;
    

    then allocate it inside your constructor and connect whichever slots and signals you want

    Widget::Widget(QWidget *parent) : QWidget(parent)
    {
      //...
      secondWidget = new SecondWidget();
      connect(bt, &QPushButton::clicked, secondWidget, &SecondWidget::show);
    }
    

    and finally release the memory inside your constructor:

    Widget::~Widget()
    {
      delete secondWidget;
    }
    

    Otherwise as I said you are basically creating a reference to a memory block and right after you leave your constructor that reference gets destroyed since it runs out of scope.


    EDIT 2:

    Here is a small example how to do it if you want secondWidget as a child to your main widget:

    main.cpp

    #include "widget.h"
    #include 
    
    int main(int argc, char *argv[])
    {
      QApplication a(argc, argv);
      Widget w;
      w.show();
    
      return a.exec();
    }
    

    widget.hpp

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include 
    #include "secondwidget.h"
    
    namespace Ui {
    class Widget;
    }
    
    class Widget : public QWidget
    {
      Q_OBJECT
    
    public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
    
    private:
      Ui::Widget *ui;
    };
    
    #endif // WIDGET_H
    

    widget.cpp

    #include "widget.h"
    #include "ui_widget.h"
    
    Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
    {
      ui->setupUi(this);
      SecondWidget *secondWidget = new SecondWidget(this); # secondWidget is now officially adopted by Widget
      # If you skip the parent assignment inside SecondWidget you can call secondWidget->setParent(this) here
      connect(ui->pushButton, SIGNAL(clicked(bool)), secondWidget, SLOT(show()));
    }
    
    Widget::~Widget()
    {
      delete ui;
    }
    

    secondwidget.hpp

    #ifndef SECONDWIDGET_H
    #define SECONDWIDGET_H
    
    #include 
    #include 
    
    class SecondWidget : public QDialog
    {
      Q_OBJECT
    public:
      explicit SecondWidget(QWidget *parent = 0);
      ~SecondWidget();
    };
    
    #endif // SECONDWIDGET_H
    

    secondwidget.cpp

    #include "secondwidget.h"
    #include 
    #include 
    
    SecondWidget::SecondWidget(QWidget *parent) : QDialog(parent)
    {
      # If you don't call the constructor of your superclass you can still assign a parent by invoking setParent(parent) here
      QFormLayout *layout = new QFormLayout();
      QLabel *label = new QLabel("SecondWidget here");
      layout->addWidget(label); # Transfer ownership of label to SecondWidget
      setLayout(layout);
    }
    
    SecondWidget::~SecondWidget()
    {
    
    }
    

    Note the setParent(parent) inside the SecondWidget's constructor. You either have to invoke the superclass constructor (as you have done) or manually call setParent(parent). If you don't do that yoursecondWidgetwill not be assigned as a child to your main widget where you create it and thus you will produce a memory leak. You can also invokesecondWidget->setParent(this)` from within the constructor of your main widget in order to set the parent.

    Here is how you can check if the parent-child hierarchy is okay:

    • To each QObject that you have (QWidget, QLayout etc. are all subclasses of QObject) assign an object name using QObject::setObjectName("some name")

    • At the end of both of your constructors add:

      for(int i = 0; i < this->children().count(); i++)
        std::cout << this->children()[i]->objectName().toStdString() << std::endl; // Add #include  to get the output
      

      which basically traverses all the children of this (Widget or SecondWidget) and displays its children. In my case I got

          label              // Printing out children of SecondWidget
          formLayout         // Printing out children of SecondWidget
          gridLayout         // Printing out children of Widget
          pushButton         // Printing out children of Widget
          main second widget // Printing out children of Widget
      

    once I launched my application.

    EDIT 3: Ah, I didn't notice that you are calling the QWidget(parent) in you SecondWidget constructor. This also does the trick so you don't need setParent(parent). I have altered my second EDIT.

提交回复
热议问题