Qt QGraphicsObject上的deleteLater vs removeItem

Qt QGraphicsObject上的deleteLater vs removeItem,qt,pyqt,pyqt4,pyqt5,Qt,Pyqt,Pyqt4,Pyqt5,虽然我通过PyQt使用Python中的Qt,但这个问题同样适用于纯Qt,只是语法有点不同,问题是相同的: 当我们想要在场景中处理QGraphicsItem对象时,我们调用scene.removietem(item)。当我们要在场景中处理QGraphicsObject对象时,我们调用scene.removietem(item),因为它派生自QGraphicsItem,但我们也调用item.deleteLater(),因为它派生自QObject,这是处理QObject的推荐方法(以便正确处理与该项之

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

当我们想要在场景中处理QGraphicsItem对象时,我们调用scene.removietem(item)。当我们要在场景中处理QGraphicsObject对象时,我们调用scene.removietem(item),因为它派生自QGraphicsItem,但我们也调用item.deleteLater(),因为它派生自QObject,这是处理QObject的推荐方法(以便正确处理与该项之间的挂起信号)

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

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

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


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

下面是
QGraphicsItem
析构函数的源代码(取自qt-5.7/qtbase/src/widgets/graphicsview/QGraphicsItem.cpp)。如您所见,它完成了整个清理任务,并调用了场景的内部
removietemhelper
函数(也被
removietem
调用)。因此,它似乎设计得很好,可以通过删除来处理删除

QGraphicsItem::~QGraphicsItem()
{
如果(d_ptr->isObject){
QGraphicsObject*o=静态_转换(此);
QObjectPrivate*p=QObjectPrivate::get(o);
p->wasdelected=true;
if(p->declarativeData){
if(静态_转换(p->declarativeData)->ownedByQml1){
如果(QAbstractDeclarativeData::destroyed_qml1)
QAbstractDeclarativeData::destroctedqml1(p->declarativeData,o);
}否则{
如果(QAbstractDeclarativeData::已销毁)
QAbstractDeclarativeData::destrocted(p->declarativeData,o);
}
p->declarativeData=0;
}
}
d_ptr->indextructor=1;
d_ptr->removeExtraItemCache();
#如果没有QT\u手势
if(d_ptr->isObject&&!d_ptr->gestureContext.isEmpty()){
QGraphicsObject*o=静态_转换(此);
如果(QGestureManager*manager=QGestureManager::instance()){
const auto types=d_ptr->gestureContext.keys();//修复:直接在地图上迭代?
for(Qt::GestureType:types)
管理器->清除缓存属性(o,类型);
}
}
#恩迪夫
clearFocus();
setFocusProxy(0);
//更新焦点范围项目ptr。
QGraphicsItem*p=d_ptr->parent;
while(p){
if(p->flags()&ItemIsFocusScope){
如果(p->d_ptr->focusScopeItem==此)
p->d_ptr->focusScopeItem=0;
打破
}
p=p->d_ptr->parent;
}
如果(!d_ptr->children.isEmpty()){
而(!d_ptr->children.isEmpty())
删除d_ptr->children.first();
Q_断言(d_ptr->children.isEmpty());
}
如果(d_ptr->场景){
d_ptr->scene->d_func()->removietemhelper(这个);
}否则{
d_ptr->resetFocusProxy();
setParentItem(0);
}
#如果没有图形效果
删除d_ptr->graphicsEffect;
#endif//QT\u无图形效果
如果(数据采集->转换数据){
对于(int i=0;itransformData->graphicsTransforms.size();++i){
qgraphicstrasform*t=d_ptr->transformData->graphicstrasforms.at(i);
静态_cast(t->d_ptr.data())->item=0;
删除t;
}
}
删除d_ptr->转换数据;
if(QGraphicsItemCustomDataStore*dataStore=qt_dataStore())
数据存储->数据。删除(此);
}
另一种方法是在从场景中删除项目之前不调用deleteLater(),但这需要手动断开项目与其他对象的连接。这与测试self.scene()在插槽中没有类似的缺点,并且很容易忘记断开插槽

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

如果该项的任何方法都不在调用堆栈上,只需
delete item

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

Qt的类大多设计得很好,因此遵循以下原则。
QGraphicsObject
是一个
QObject
的可替代对象,您可以将其视为一个
QObject
,而不必担心它恰好也是一个
QGraphicsItem

就这些。它会一次解决你所有的问题

您几乎不必直接调用
scene.removietem
:管理项目的生命周期,场景将跟随您。这就像
QWidget
QLayout
之间的交互:由布局管理的小部件