虽然我通过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(),你认为这是安全的吗?
答案 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 Principle。 TFArray
是可替代的delete item
,您可以将其视为QObject::deleteLater
,而不必担心它恰好是QGraphicsObject
也是。
这就是它的全部内容。它将一气呵成地解决您的所有问题。
您几乎不必直接致电QObject
:管理物品的生命周期,现场将为您跟踪。它就像QObject
和QGraphicsItem
之间的交互一样:由布局管理的小部件仍然是可破坏的,当小部件被销毁时,布局将忘记小部件。