【问题标题】:Find the sender of the `destroyed(QObject*)` signal找到 `destroyed(QObject*)` 信号的发送者
【发布时间】:2015-12-28 18:57:05
【问题描述】:

我目前想知道如何合理使用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();
}

这是它的输出:

QWidget(0x1e9e1e0) QObject(0x1e5c530)

因此,据推测,信号是从QObject 的 d-tor 发出的,因此当为QTimer 调用插槽时,只剩下QObject 基址。但是,QWidget 的 d-tor 似乎被拦截了,因为它仍然从插槽中将自己标识为 QWidget

还有问题

假设我们有一个计时器池,它在QList&lt;QTimer *&gt; 中组织了几个计时器:

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&lt;QTimer *&gt;(object)

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

  1. QObjects 存储在列表中。然后,每次使用列表中的项目时,我都必须沮丧。不过,这可以使用static_cast 来完成,因为我知道列表中只有QTimers,所以不需要dynamic_castqobject_cast
  2. removeOne 的原样使用iterator 遍历列表,然后将每个QTimer 项目直接与QObject 进行比较。然后使用QList::erase 之类的。
  3. static_cast 甚至 reinterpret_cast QObjectQtimer

我该怎么办?

谢谢你,圣诞快乐,新年快乐 :-)[*]

[*]:当这个问题完成后会清理它。

【问题讨论】:

    标签: c++ qt signals-slots


    【解决方案1】:

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

    【讨论】:

    • 当然,这需要实际设置 objectName。但是我最终会遍历列表,所以我也可以比较我想的指针。或者在QObject::findChild()中隐藏遍历,类似。
    • 你提出了一个我没有真正意识到的重要含义:destroyed() 信号是从 QObject d-tor 发出的,所以至少 QObject 基础仍然是完全活跃和可用的。
    • 对。这是一种 hack,但我以前为各种 QWidgets 做过。如果为它设置了数据结构,那么设置 objectName 并不是什么大问题。
    • 我什至可能不需要。 static_cast 只是一些指针对齐魔术,无需访问指向的对象(与 dynamic/qobject_cast 不同),因此只需转换然后直接比较指针就可以了。不过会对此进行一些研究。
    【解决方案2】:

    您的问题似乎很明显是对象所有权之一;特别是,如何传达谁负责销毁一个对象。如果您的 Pool 对象拥有 QTimer 对象(因此用户不应delete 它们),请通过界面明确说明,例如从您的getTimer 方法。我不是很精通 Qt,但如果你真的想传输从方法返回的对象的所有权,从而让用户负责删除它,你可能会返回 @987654324 @。

    【讨论】:

    • Qt 中有几种所有权模式。转让所有权通常涉及某种形式,例如 takeFoo()。然而,在这里返回一个引用是不切实际的,因为 QTimer 是多态的,无论如何最终都会获取返回的引用的地址......
    • 但我认为 Qt API 的所有权政策存在问题,+1。
    • 但如果您担心多态性,QTimer&amp;QTimer* 一样多态。它只是按您不想要的值返回。此外,如果您确实需要它,则可以获取返回的引用的地址,它为您提供了被引用对象的地址(没有引用地址之类的东西),这是您原来的@987654327 @。这将在用户代码中花费您一个额外的字符 (&),并且清楚地传达了池保留所有权这一事实。
    【解决方案3】:

    只需直接投射:

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

    【讨论】:

      【解决方案4】:

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

      QList<QPointer<QTimer>> timers;
      

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

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

      【讨论】:

        猜你喜欢
        • 2016-10-03
        • 2015-05-06
        • 2017-11-22
        • 1970-01-01
        • 1970-01-01
        • 2015-08-20
        • 1970-01-01
        • 1970-01-01
        • 2012-11-15
        相关资源
        最近更新 更多