Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the sender of the `destroyed(QObject*)` signal

I am currently wondering how to reasonably use the QObject::destroyed(QObject*) signal.

An observation

I noticed that QWidget-derived objects are treated slightly different. Consider the following small self-contained and compiling example:

/* sscce.pro:
QT += core gui widgets
CONFIG += c++11
TARGET = sscce
TEMPLATE = app
SOURCES += main.cpp
*/

#include <QApplication>
#include <QPushButton>
#include <QTimer>
#include <QtDebug>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QPushButton *button = new QPushButton;
    QObject::connect(button, &QPushButton::destroyed,
        [=](QObject *o) { qDebug() << o; });

    delete button;

    QTimer *timer = new QTimer;
    QObject::connect(timer, &QTimer::destroyed,
        [=](QObject *o) { qDebug() << o; });

    delete timer;

    return app.exec();
}

This is its output:

QWidget(0x1e9e1e0)
QObject(0x1e5c530)

So presumably, the signal is emitted from QObject's d-tor, so only the QObject base remains when the slot is called for the QTimer. However, QWidget's d-tor seems to intercept as it still identifies itself as a QWidget from the slot.

And the problem

Let's assume we have a timer pool that organizes a couple of timers in a QList<QTimer *>:

struct Pool {
    QTimer *getTimer() {
        return timers.at(/* some clever logic here */);
    }        

    QList<QTimer *> timers;
};

Now an incautious user might delete the timer that was borrowed to him/her. Well, we can react and simply remove that timer from the list. A slot will do the trick:

Pool::Pool() {
    /* for each timer created */
    connect(theTimer, SIGNAL(destroyed(QObject*),
        this, SLOT(timerDestroyed(QObject*));
}

void Pool::timerDeleted(QObject *object) {
    QTimer *theTimer = /* hrm. */
    timers.removeOne(theTimer);
}

But what now? Hrm. When the slot is called, the QTimer already is in destruction and partially destroyed - only its QObject base remains. So I objously cannot qobject_cast<QTimer *>(object).

To resolve this issue, I could think of the following tricks:

  1. Store QObjects in the list. Then I'd have to downcast every time I use an item from the list. This could be done using static_cast, though, as I know there will only be QTimers in the list, so no need for dynamic_cast or qobject_cast.
  2. Insteat of removeOne traverse the list using an iterator and then compare each QTimer item directly to the QObject. Then use QList::erase or such.
  3. static_cast or even reinterpret_cast the QObject to a Qtimer nonetheless.

What should I do?

Thank you, merry christmas and happy new year :-)[*]

[*]: Will clean this up when this question is done.

like image 535
Kamajii Avatar asked Oct 18 '22 19:10

Kamajii


2 Answers

If you're looking for tricks, you could simply use the base QObject objectName and remove the destroyed timer based on that.

like image 69
Chuck Claunch Avatar answered Oct 21 '22 17:10

Chuck Claunch


It seems clear that your problem is one of object ownership; in particular, how to convey who is responsible for destroying an object. If your Pool object owns the QTimer objects (and thus the user should not delete them), make it clear through the interface, for example returning a QTimer& instead of a QTimer* from your getTimer method. I'm not really well versed in Qt, but if you actually wanted to transmit ownership of the object returned from a method and thus make the user responsible of its deletion, you'd likely return a std::unique_ptr<QTimer>.

like image 45
Javier Martín Avatar answered Oct 21 '22 16:10

Javier Martín