deleteLater vs QGraphicsObject上的removeItem

时间:2016-12-06 20:51:46

标签: qt pyqt pyqt4 pyqt5

虽然我通过PyQt使用Python中的Qt,但这个问题同样适用于纯Qt,只是语法有点不同,问题是一样的:

当我们想在场景中处理QGraphicsItem对象时,我们调用scene.removeItem(item)。当我们想在场景中处理QGraphicsObject对象时,我们调用scene.removeItem(item),因为它派生自QGraphicsItem,但我们也调用item.deleteLater(),因为它派生自QObject,这是推荐的处理方式QObjects(以便正确处理进出项目的待处理信号)。

问题是,由于deleteLater()的功能,可以在项目从场景中删除后调用对象项中的槽。这要求我们在插槽中测试self.scene()为None。但这很容易出错,因为很容易忘记这样做,如果调用slot,则忘记这会导致异常。

另一种方法是在从场景中删除项目之前不调用deleteLater(),但这需要手动将项目与其他对象断开连接。这与在插槽中测试self.scene()为None的情况类似,并且很容易忘记断开插槽。

减轻这个错误源(如果没有隐藏的陷阱)的更好方法是当item是QGraphicsObject时不调用scene.removeItem(item),并且JUST调用它的deleteLater():它似乎 ,基于一些简单的测试,当场景最终被破坏时,场景会自动从列表中删除它。但是,我找不到任何说明这一点的Qt文档,我可能只是幸运的;也许在更现实的情况下,我会得到内存泄漏或崩溃。

因此,当item是QGraphicsObject时,我倾向于调用deleteLater()而不调用removeItem(),你认为这是安全的吗?

2 个答案:

答案 0 :(得分:3)

以下是QGraphicsItem析构函数的源代码(取自 qt-5.7 / qtbase / src / widgets / graphicsview / qgraphicsitem.cpp )。正如您所看到的,它完成了整个清理工作,并调用了场景的内部removeItemHelper函数(也由removeItem调用)。因此,它看起来设计得很好,可以通过删除来处理删除。

QGraphicsItem::~QGraphicsItem()
{
    if (d_ptr->isObject) {
        QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
        QObjectPrivate *p = QObjectPrivate::get(o);
        p->wasDeleted = true;
        if (p->declarativeData) {
            if (static_cast<QAbstractDeclarativeDataImpl*>(p->declarativeData)->ownedByQml1) {
                if (QAbstractDeclarativeData::destroyed_qml1)
                    QAbstractDeclarativeData::destroyed_qml1(p->declarativeData, o);
            } else {
                if (QAbstractDeclarativeData::destroyed)
                    QAbstractDeclarativeData::destroyed(p->declarativeData, o);
            }
            p->declarativeData = 0;
        }
    }

    d_ptr->inDestructor = 1;
    d_ptr->removeExtraItemCache();

#ifndef QT_NO_GESTURES
    if (d_ptr->isObject && !d_ptr->gestureContext.isEmpty()) {
        QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
        if (QGestureManager *manager = QGestureManager::instance()) {
            const auto types  = d_ptr->gestureContext.keys(); // FIXME: iterate over the map directly?
            for (Qt::GestureType type : types)
                manager->cleanupCachedGestures(o, type);
        }
    }
#endif

    clearFocus();
    setFocusProxy(0);

    // Update focus scope item ptr.
    QGraphicsItem *p = d_ptr->parent;
    while (p) {
        if (p->flags() & ItemIsFocusScope) {
            if (p->d_ptr->focusScopeItem == this)
                p->d_ptr->focusScopeItem = 0;
            break;
        }
        p = p->d_ptr->parent;
    }

    if (!d_ptr->children.isEmpty()) {
        while (!d_ptr->children.isEmpty())
            delete d_ptr->children.first();
        Q_ASSERT(d_ptr->children.isEmpty());
    }

    if (d_ptr->scene) {
        d_ptr->scene->d_func()->removeItemHelper(this);
    } else {
        d_ptr->resetFocusProxy();
        setParentItem(0);
    }

#ifndef QT_NO_GRAPHICSEFFECT
    delete d_ptr->graphicsEffect;
#endif //QT_NO_GRAPHICSEFFECT
    if (d_ptr->transformData) {
        for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) {
            QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i);
            static_cast<QGraphicsTransformPrivate *>(t->d_ptr.data())->item = 0;
            delete t;
        }
    }
    delete d_ptr->transformData;

    if (QGraphicsItemCustomDataStore *dataStore = qt_dataStore())
        dataStore->data.remove(this);
}

答案 1 :(得分:1)

  

另一种方法是在从场景中删除项目之前不调用deleteLater(),但这需要手动将项目与其他对象断开连接。这与在插槽中测试self.scene()为None的情况类似,并且很容易忘记断开插槽。

首先,如果您的目标是销毁该项目,则没有理由从场景中手动删除项目。场景跟踪项目生命周期。所以你需要做的就是用适当的方法销毁这个项目。

如果项目的方法都不在调用堆栈中,只需height

如果项目的方法可能在调用堆栈上,请使用width方法。

Qt的课程大多设计得很好,因此遵循Liskov Substitution PrincipleTFArray是可替代的delete item,您可以将其视为QObject::deleteLater,而不必担心它恰好是QGraphicsObject也是。

这就是它的全部内容。它将一气呵成地解决您的所有问题。

您几乎不必直接致电QObject:管理物品的生命周期,现场将为您跟踪。它就像QObjectQGraphicsItem之间的交互一样:由布局管理的小部件仍然是可破坏的,当小部件被销毁时,布局将忘记小部件。