c++ 查找"已销毁(QObject*)“信号的发送方

exdqitrt  于 2023-01-03  发布在  其他
关注(0)|答案(5)|浏览(127)

我现在想知道如何合理地使用QObject::destroyed(QObject*)信号。

一个观察

我注意到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();
}

以下是它的输出:

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

现在,一个不小心的用户可能会删除借用给他/她的计时器。那么,我们可以做出React,简单地从列表中删除该计时器。一个插槽就可以做到这一点:

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
1.代替removeOne,使用iterator遍历列表,然后将每个QTimer项直接与QObject进行比较。

  1. static_cast或甚至reinterpret_castQObjectQtimer
    我该怎么办?
kxkpmulp

kxkpmulp1#

如果您正在寻找技巧,您可以简单地使用基本QObject objectName并基于此删除已销毁的计时器。

cbeh67ev

cbeh67ev2#

很明显,您的问题是对象所有权问题;特别是,如何传达谁负责销毁一个对象。如果你的Pool对象拥有QTimer对象(因此用户不应该delete它们),通过接口使其清楚,例如从getTimer方法返回QTimer&而不是QTimer*。但如果您实际上想 * 传输方法返回的对象的所有权 *,从而让用户负责删除它,则可能会返回一个std::unique_ptr<QTimer>

fv2wmkja

fv2wmkja3#

直接投吧:

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

ercv8c1e4#

你可以将你的列表基于QPointer而不是原始指针。

QList<QPointer<QTimer>> timers;

现在,当列表中的一个计时器消失时,列表中相应的条目将自动被清除,但它 * 不会 * 被删除!但是当您通过getTimer()方法访问计时器时,其计时器已被删除的条目将返回nullptr(而不是悬空指针)。
是的,QWidget在它自己的析构函数中发出destroyed(),这就是为什么在这种情况下你会看到一个真实的的QWidget,其他人都使用QObject的实现。

zdwk9cvp

zdwk9cvp5#

反之则安全。请将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)。

相关问题