找到“已销毁(QObject*)”信号的发送方
Find the sender of the `destroyed (QObject*)` signal
我目前想知道如何合理地使用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;
};
现在,不谨慎的用户可能会删除借给他/她的计时器。好吧,我们可以做出反应,只需从列表中删除该计时器即可。老虎机可以解决问题:
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)
.
要解决此问题,我可以想到以下技巧:
在列表中存储
QObject
。然后,每次使用列表中的项目时,我都必须向下抛弃。这可以使用static_cast
来完成,因为我知道列表中只有QTimer
s,所以不需要dynamic_cast
或qobject_cast
。Insteat of
removeOne
使用iterator
遍历列表,然后将每个QTimer
项直接与QObject
进行比较。然后使用QList::erase
等。尽管如此,
static_cast
甚至reinterpret_cast
QObject
Qtimer
。
我该怎么办?
如果你正在寻找技巧,你可以简单地使用基本的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(。
- QObject::连接无法将信号连接到*this*对象的插槽
- QObject::连接不起作用 - 使用函数语法找不到信号
- 在另一个线程上发出 QObject 信号的正确方法?
- 可以在 QObject::connect() C++中连接 QML 对象现有信号?
- 当通过引用传递 QObject 时,C++信号的参数在 QML 中显示为"未定义"
- 我可以交换QObject儿童保持信号和连接的插槽吗?
- 删除 QObject 后 Qt 信号仍在触发
- 替换 QObject 与信号和插槽一起使用时的异常
- .Qt :阻止 QObject 接收信号
- Qt-Qml连接到上下文属性的QObject属性的信号
- QObject::connect:未找到信号
- 将QObject接口信号连接到lambda插槽
- QObject::connect:没有这样的信号progressbarV::keyReleaseEvent()
- 在传递的对象即将被销毁之前,发出将QObject指针作为参数传递的信号是否安全
- 如何使用新的QObject::连接语法与函数指针将QSslSocket::错误信号连接到插槽
- QObject::connect:在 c++ Qt 5.3 中连接 qml 信号时没有这样的信号
- 在信号/插槽处理过程中删除QObject
- 找到“已销毁(QObject*)”信号的发送方
- QObject::connect:没有这样的信号mouseReleaseEvent
- 使用QObject::connect()时,类型签名是否重要,用于将函数连接到信号