0

I have a C++ program using Qt, with a GUI QObject that has a ComboBox (let's call it foo) linked to a function (bar()). This QObject is called object A.

The combo box is linked in the constructor:

  connect(foo, SIGNAL(currentIndexChanged(int)), SLOT(bar(int)));

In bar(int), this function is called at the end of the function:

  inspector->update();

In the inspector's update function, this happens:

  • A is deleted (delete A;, pretty much - it was created with new).

  • A new instance of the same type as A is created, let's call it object B.

This is initialized and runs fine for a moment, then everything goes horribly wrong.

The program segfaults, but only due to a Qt error. GDB doesn't help much, but running the program through Valgrind memcheck produces a torrent of invalid reads such as this:

==23410== Invalid read of size 8
==23410==    at 0xFE92D7A: QMetaObject::activate(QObject*, int, int, void**) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.9.5)
==23410==    by 0x79DDC94: QComboBox::currentIndexChanged(QString const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.9.5)
==23410==    by 0x79DFB2D: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.9.5)
...
==23410==  Address 0x3218ca68 is 8 bytes inside a block of size 48 free'd
==23410==    at 0x4C3123B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23410==    by 0xFE9119A: QObjectPrivate::deleteChildren() (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.9.5)
==23410==    by 0x7913D4B: QWidget::~QWidget() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.9.5)

This seems to me like the signal from foo is being sent again, and is trying to call bar() of object A which doesn't exist. But how is this possible? Surely delete A should also stop any signals from getting through? Also, I am only changing the value of the combo box once - why would multiple signals be sent?

I've tried blocking signals, and I've tried rearranging things, but with no success.

Here is the entire relevant Valgrind memcheck log.

Is this a bug with Qt? Or am I doing something wrong? Any help, including help with debugging further, is very much appreciated.

  • 2
    You should really provide a [mcve]. Note, however, that calling `delete` on a `QObject *` is generally a bad idea. Rather than `delete A` you should probably use [`A->deleteLater()`](https://doc.qt.io/qt-5/qobject.html#deleteLater). – G.M. Jun 09 '19 at 11:54
  • 1
    You should stop using the old `SIGNAL` and `SLOT` macros that only detect connection errors at run-time (and are slow since they are based on string comparisons). Instead embrace the new, compile-time checked, syntax that's based on member function pointers. That also lets you avoid the code bloating `Q_OBJECT` macro in many cases. – Jesper Juhl Jun 09 '19 at 11:56
  • It looks like you delete A in code called by a signal from A. The execution then continues where the signal was emitted, operating on an already deleted `this`. (See also https://stackoverflow.com/questions/4888189/how-delete-and-deletelater-works-with-regards-to-signals-and-slots-in-qt/4889395#4889395). I would consider to use deleteLater(), or, probably better, a QueuedConnection for the signal/slot connection. – Frank Osterfeld Jun 09 '19 at 13:05
  • G.M. "Note, however, that calling delete on a QObject * is generally a bad idea". I don't agree, delete is usually just fine, using deleteLater() by default without thinking make the code just less predictable and might hide problems and create new ones (e.g. if the object holds resources or continues to do something, between the deleteLater() and the actual deletion). – Frank Osterfeld Jun 09 '19 at 13:09
  • deleteLater doesn't fix the problem. I'll see if I can put together a minimum reproducible example, but then again it is C++ and Qt so 'minimal' doesn't mean much... –  Jun 09 '19 at 16:07

1 Answers1

0

Try to do this:

  • First Add a member QMetaObject::Connection foo_barConnect; to your class.

  • In the Constructor, save your connect in foo_barConnect:

foo_barConnect = connect(foo, SIGNAL(currentIndexChanged(int)), SLOT(bar(int)));
  • In the update() function, before delete add:
// Check if `foo_barConnect` is valid (connected)
if(foo_barConnect){
    // Disconnect foo_bar connection
    disconnect(foo_barConnect);
}
  • If you re-new the object and you need the connection, don't forget to re-connect and store the connection in foo_barConnect.

Hope it helps you.

Tom Kim
  • 416
  • 2
  • 4