Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-105473

QtPrivate::assertObjectType for child-to-parent signal on destruction when parent destroys children before signals disconnection

    XMLWordPrintable

Details

    • All

    Description

      Introduced by QTBUG-33908.

      Let's have a class for the child item:

      class TestWidget : public QWidget
      {
          Q_OBJECT
      
      public:
          TestWidget(QWidget *parent = nullptr)
              : QWidget(parent) { }
      
          ~TestWidget()
          {
              cancelLoad();
          }
      
      signals:
          void updated() const;
      
      private:
          void cancelLoad()
          {
              emit updated();
          }
      };
      

      and a class for the parent item:

      class MainWindow : public QMainWindow
      {
          Q_OBJECT
          
      public:
          MainWindow(QWidget *parent = nullptr)
              : QMainWindow(parent)
          {
              m_test = new TestWidget(this);
      
              connect(m_test, &TestWidget::updated,
                  this, &MainWindow::updateContent);
          }
      
      private:
          void updateContent() { }
      
          TestWidget *m_test = nullptr;
      };
      

      On main window close the program crashes with the following error:

      ASSERT failure in MainWindow: "Called object is not of the correct type (class destructor may have already run)", file /mnt/store/Development/Qt/6.3.1/gcc_64/include/QtCore/qobjectdefs_impl.h, line 155
      
      Stack trace
      1  __GI_raise                                                                                                                                                raise.c            50   0x7ffff6e7700b 
      2  __GI_abort                                                                                                                                                abort.c            79   0x7ffff6e56859 
      3  qAbort                                                                                                                                                    qglobal.cpp        3397 0x7ffff72d541a 
      4  qt_message_fatal                                                                                                                                          qlogging.cpp       1879 0x7ffff72d5b4c 
      5  QMessageLogger::fatal                                                                                                                                     qlogging.cpp       881  0x7ffff72d5b4c 
      6  qt_assert_x                                                                                                                                               qlogging.h         97   0x7ffff72d5414 
      7  QtPrivate::assertObjectType<MainWindow>                                                                                                                   qobjectdefs_impl.h 155  0x5555555581f4 
      8  QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, void (MainWindow:: *)()>::call(void (MainWindow:: *)(), MainWindow *, void * *) qobjectdefs_impl.h 170  0x5555555580e0 
      9  QtPrivate::FunctionPointer<void (MainWindow:: *)()>::call<QtPrivate::List<>, void>(void (MainWindow:: *)(), MainWindow *, void * *)                       qobjectdefs_impl.h 208  0x555555558097 
      10 QtPrivate::QSlotObject<void (MainWindow:: *)(), QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void * *, bool *)            qobjectdefs_impl.h 419  0x555555557ffd 
      11 QtPrivate::QSlotObjectBase::call                                                                                                                          qobjectdefs_impl.h 399  0x7ffff73c229a 
      12 doActivate<false>                                                                                                                                         qobject.cpp        3921 0x7ffff73c229a 
      13 TestWidget::updated                                                                                                                                       main.moc           133  0x555555557733 
      14 TestWidget::cancelLoad                                                                                                                                    main.cpp           23   0x555555557a92 
      15 TestWidget::~TestWidget                                                                                                                                   main.cpp           14   0x555555557a22 
      16 TestWidget::~TestWidget                                                                                                                                   main.cpp           15   0x555555557a58 
      17 QObjectPrivate::deleteChildren                                                                                                                            qlist.h            415  0x7ffff73bc20a 
      18 QWidget::~QWidget                                                                                                                                         qwidget.cpp        1525 0x7ffff7a6aee8 
      19 MainWindow::~MainWindow                                                                                                                                   main.cpp           27   0x55555555826e 
      20 main                                                                                                                                                      main.cpp           50   0x555555557497 
      

      This doesn't happen on pure objects (non-widgets), because ~QObject() disconnects signals and then deletes children, but ~QWidget() deletes children before signals disconnection in ~QObject().

      Another example is a class derived from QGraphicsScene with a slot that is connected to signals from graphics objects. ~QGraphicsScene() calls QGraphicsScene::clear() which deletes all graphics items without any prior disconnection.

      In general, any class A that destroys some "children" that could be connected to an instance of class B that is derived from A could be affected.

      To summarize current problems with signals and slots:

      1. From the sender's view: (1) an object must not send signals from its destructor and (2) must not call any function that could send a signal or (3) disconnect in the destructor all signals of this class type.
      While (1) could be done, how do we ensure (2)?
      How to disconnect only signals only from a specific type (3)? Adding `disconnect()` statements manually in the destructor requires a lot of work, it's a boilerplate code and error-prone.

      2. From the receiver's view: an object must disconnect all slots of this class type. Again, how to do that?

      3. From the user's of senders and receivers: the above changes should be done to all classes, including Qt code and other libraries. As this is almost impossible, one can only use signal-slot connection if: (1) a sender isn't a (indirect) child of the receiver or (2) sender won't emit signals in destructor and won't call any function that emits. (1) is hard, because an item could be moved to a different parent and that new parent could be a receiver. (2) is almost impossible, especially if the source code isn't available.

      Attachments

        Issue Links

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              thiago Thiago Macieira
              nkrupenko.nvidia Nikita Krupenko
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Gerrit Reviews

                  There are no open Gerrit changes