I've encountered this problem on several occasions, with objects created dynamically, regardless of whether they were created in QML or C++. The objects are deleted while still in use, causing hard crashes for no apparent reason. The objects are still referenced and parented to other objects all the way down to the root object, so I find it strange for QML to delete those objects while their refcount is still above zero.
So far the only solution I found was to create the objects in C++ and set the ownership to CPP explicitly, making it impossible to delete the objects from QML.
At first I assumed it may be an issue with parenting, since I was using QObject
derived classes, and the QML method of dynamic instantiation passes an Item
for a parent, whereas QtObject
doesn't even come with a parent property - it is not exposed from QObject
.
But then I tried with a Qobject
derived which exposes and uses parenting and finally even tried using Item
just for the sake of being sure that the objects are properly parented, and yet this behavior still persists.
Here is an example that produces this behavior, unfortunately I could not flatten it down to a single source because the deep nesting of Component
s breaks it:
// ObjMain.qml
Item {
property ListModel list : ListModel { }
Component.onCompleted: console.log("created " + this + " with parent " + parent)
Component.onDestruction: console.log("deleted " + this)
}
// Uimain.qml
Item {
id: main
width: childrenRect.width
height: childrenRect.height
property Item object
property bool expanded : true
Loader {
id: li
x: 50
y: 50
active: expanded && object && object.list.count
width: childrenRect.width
height: childrenRect.height
sourceComponent: listView
}
Component {
id: listView
ListView {
width: contentItem.childrenRect.width
height: contentItem.childrenRect.height
model: object.list
delegate: Item {
id: p
width: childrenRect.width
height: childrenRect.height
Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
}
}
}
Rectangle {
width: 50
height: 50
color: "red"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: {
if (mouse.button == Qt.RightButton) {
expanded = !expanded
} else {
object.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(object) })
}
}
}
}
}
// main.qml
Window {
visible: true
width: 1280
height: 720
ObjMain {
id: obj
}
Uimain {
object: obj
}
}
The example is a trivial object tree builder, with the left button adding a leaf to the node and the right button collapsing the node. All it takes to reproduce the bug is to create a node with depth of 3 and then collapse and expand the root node, upon which the console output shows:
qml: created ObjMain_QMLTYPE_0(0x1e15bb8) with parent QQuickRootItem(0x1e15ca8)
qml: created ObjMain_QMLTYPE_0(0x1e5afc8) with parent ObjMain_QMLTYPE_0(0x1e15bb8)
qml: created ObjMain_QMLTYPE_0(0x1e30f58) with parent ObjMain_QMLTYPE_0(0x1e5afc8)
qml: deleted ObjMain_QMLTYPE_0(0x1e30f58)
The object
of the deepest node is deleted for no reason, even though it is parented to the parent node Item
and referenced in the JS object in the list model. Attempting to add a new node to the deepest node crashes the program.
The behavior is consistent, regardless of the structure of the tree, only the second level of nodes survives, all deeper nodes are lost when the tree is collapsed.
The fault does not lie in the list model being used as storage, I've tested with a JS array and a QList
and the objects are still lost. This example uses a list model merely to save the extra implementation of a C++ model. The sole remedy I found so far was to deny QML ownership of the objects altogether. Although this example produces rather consistent behavior, in production code the spontaneous deletions are often completely arbitrary.
In regard to the garbage collector - I've tested it before, and noticed it is quite liberal - creating and deleting objects a 100 MB of ram worth did not trigger the garbage collection to release that memory, and yet in this case only a few objects, worth a few hundred bytes are being hastily deleted.
According to the documentation, objects which have a parent or are referenced by JS should not be deleted, and in my case, both are valid:
The object is owned by JavaScript. When the object is returned to QML as the return value of a method call, QML will track it and delete it if there are no remaining JavaScript references to it and it has no QObject::parent()
As mentioned in Filip's answer, this does not happen if the objects are created by a function which is not in an object that gets deleted, so it may have something to do with the vaguely mentioned JS state associated with QML objects, but I am essentially still in the dark as of why the deletion happens, so the question is effectively still unanswered.
Any ideas what causes this?
UPDATE: Nine months later still zero development on this critical bug. Meanwhile I discovered several additional scenarios where objects still in use are deleted, scenarios in which it doesn't matter where the object was created and the workaround to simply create the objects in the main qml file doesn't apply. The strangest part is the objects are not being destroyed when they are being "un-referenced" but as they are being "re-referenced". That is, they are not being destroyed when the visual objects referencing them are getting destroyed, but when they are being re-created.
The good news is that it is still possible to set the ownership to C++ even for objects, which are created in QML, so the flexibility of object creation in QML is not lost. There is the minor inconvenience to call a function to protect and delete every object, but at least you avoid the buggy lifetime management of QtQuick. Gotta love the "convenience" of QML though - being forced back to manual object lifetime management.