繁体   English   中英

找到 `destroyed (QObject*)` 信号的发送者

[英]Find the sender of the `destroyed (QObject*)` signal

我目前想知道如何合理地使用QObject::destroyed(QObject*) signal

一个观察

我注意到QWidget派生对象的处理方式略有不同。 考虑以下小型独立编译示例:

/* 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();
}

这是它的 output:

QWidget(0x1e9e1e0)
QObject(0x1e5c530)

因此可以推测,信号是从QObject的 d-tor 发出的,因此当为QTimer调用插槽时,只保留QObject基。 然而, QWidget的 d-tor 似乎拦截了,因为它仍然将自己标识为插槽中的QWidget

而问题

假设我们有一个定时器池,它在QList<QTimer *>中组织了几个定时器:

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

    QList<QTimer *> timers;
};

现在,一个粗心的用户可能会删除借给他/她的计时器。 好吧,我们可以做出反应,只需从列表中删除该计时器即可。 插槽可以解决问题:

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);
}

但是现在呢? 嗯。 当插槽被调用时, QTimer已经处于破坏状态并且部分被破坏 - 只剩下它的QObject基础。 所以我坚决不能qobject_cast<QTimer *>(object)

为了解决这个问题,我想到了以下技巧:

  1. QObject存储在列表中。 然后每次我使用列表中的项目时我都不得不沮丧。 不过,这可以使用static_cast来完成,因为我知道列表中只会有QTimer ,所以不需要dynamic_castqobject_cast

  2. removeOne 的removeOne使用iterator遍历列表,然后将每个QTimer项目直接与QObject进行比较。 然后使用QList::erase等。

  3. static_cast或什至将QObject reinterpret_castQtimer n.netheless。

我应该怎么办?

如果你正在寻找技巧,你可以简单地使用基础QObject objectName并根据它删除被破坏的计时器。

很明显,你的问题是对象所有权之一; 特别是,如何传达谁负责摧毁一个物体。 如果您的Pool对象拥有 QTimer对象(因此用户不应delete它们),请通过界面清除它,例如从getTimer方法返回QTimer&而不是QTimer* 我不是很精通Qt,但是如果你真的想要传输从方法返回的对象的所有权 ,从而让用户负责删除它,你可能会返回一个std::unique_ptr<QTimer>

只做直接演员:

void Pool::timerDeleted(QObject *object) {
    QTimer *theTimer = (QTimer*)object; //qobject_cast doesn't work here
//we are sure that only a timer can be a sender
    timers.removeOne(theTimer);
}

您可以将列表基于QPointer而不是原始指针。 即写

QList<QPointer<QTimer>> timers;

现在,当列表中的一个计时器消失时,列表中的相应条目将自动清除。 但它不会被删除! 但是当你通过getTimer()方法访问计时器时,一个已经删除了计时器的条目现在将返回一个nullptr (而不是一个悬空指针)。

是的, QWidget在自己的析构函数中发出destroyed() 这就是为什么在这种情况下你会看到一个真正的QWidget 其他人都使用QObject的实现。

反之则安全。 QTimer *转换为QObject *

void Pool::timerDeleted(QObject *object) {
    const auto it = std::find_if(timers.begin(), timers.end(), [object](QTimer *timer) {
        return static_cast<QObject *>(timer) == object;
    });
    Q_ASSERT(it != timers.end());
    timers.erase(it);
}

或者使用Qt 6.1中介绍的erase_if(QList &list, Predicate pred)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM