【问题标题】:deleteLater vs removeItem on a QGraphicsObjectQGraphicsObject 上的 deleteLater vs removeItem
【发布时间】:2017-04-21 15:23:19
【问题描述】:

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

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

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

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

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

所以我倾向于在 item 是 QGraphicsObject 时调用 deleteLater() 而不调用 removeItem(),你认为这样安全吗?

【问题讨论】:

  • QGraphicsItem 的源代码算作文档吗?析构函数隐式删除该项目并执行其他清理工作的全部负载 - 因此它看起来设计得很好,可以通过删除来处理删除。
  • @ekhumoro 哈哈,当我看到已针对此问题发布了新评论时,我正要查找它。您可以使用源代码中的相关代码发布答案吗?

标签: qt pyqt pyqt4 pyqt5


【解决方案1】:

下面是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);
}

【讨论】:

    【解决方案2】:

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

    首先,如果您的目标是销毁物品,则没有理由从场景中手动移除物品。场景跟踪项目的生命周期。因此,您需要做的就是通过适当的方式销毁该物品。

    如果调用堆栈中没有项的方法,则只需 delete item

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

    Qt 的类大多设计良好,因此遵循Liskov Substitution PrincipleQGraphicsObject is-substitutable-for-a QObject,您可以将其视为确实是 QObject,而不必担心它也恰好是 QGraphicsItem

    仅此而已。它会一次性解决你所有的问题。

    您几乎不必直接调用scene.removeItem:管理物品的生命周期,场景会为您跟进。就像QWidgetQLayout之间的交互:被布局管理的小部件仍然是可破坏的,当小部件被破坏时布局会忘记小部件。

    【讨论】:

      猜你喜欢
      • 2015-07-26
      • 1970-01-01
      • 2021-12-05
      • 2013-08-31
      • 1970-01-01
      • 2012-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多