How to manage lifetime of dynamically allocated QO

2019-07-07 05:08发布

问题:

I have this code:

QVariant componentFromCode(QString code) {
    QQmlComponent * component = new QQmlComponent(engine);
    engine->setObjectOwnership(component, QQmlEngine::JavaScriptOwnership);
    connect(component, &QQmlComponent::destroyed, this, &Factory::echo);
    component->setData(code.toUtf8(), QUrl());
    return QVariant::fromValue(component);
}

But Factory::echo() is never called, which means the object gets leaked every time the function is called.

This is what I have on the QML side:

onClicked: {          
    var code =
        'import QtQuick 2.3
        Rectangle {
            width: 50
            height: 50
            color: "blue"
        }
        '

    stack.push(Factory.componentFromCode(code))
    gc()
}

I explicitly set the object ownership, and explicitly call gc() to force garbage collection, but the destroyed() signal never gets emitted, therefore the object never gets deleted. From what I read this is supposed to happen automatically in QML.

Note that it works to:

var comp = Factory.componentFromCode(code)
stack.push(comp)
comp.destroy()

But it is just not convenient, I'd like to have the object destroyed automatically as it falls out of scope, or alternatively, remain alive for as long as it is referenced by QML code and be destroyed when it is no longer needed, something that might be hard/absurd to do manually in many situations.

EDIT: The stack example happened to be my actual code, but I guess it is not that good of an example, seeing how the stack taking ownership over the component is assumed. I don't get any lifetime management even in such simple cases as:

function JSfoo() {
    var obj = CXTProp.getCppQObjectStar()
    console.log(obj.objectName)
} // QObject is not collected here

or...

QtObject {
    property QtObject: CXTProp.getCppQObjectStar()
} // QObject is not collected after the object is destroyed

回答1:

I don't think the object is leaking. Try this out:

class TestObj : public QObject {
     Q_OBJECT
public:
    ~TestObj() { qDebug() << "destructor called"; }
};

class Test : public QObject {
    Q_OBJECT
public:
    explicit Test(QObject *parent = 0) : QObject(parent) {}
public slots:
    QVariant getObject() {
        QObject * obj = new TestObj;
        obj->setObjectName("that object");
        connect (obj, &TestObj::destroyed, this, &Test::echo);
        return QVariant::fromValue(obj);
    }
    void echo() { qDebug() << "it got destroyed"; }
};

and in QML:

function test() {
    var test = Test.getObject()
    console.log(test.objectName)
}

After test() the object isn't collected, and when you close the application, the echo() slot is never triggered, but the debug statement from the destructor is indeed showing up in the console:

qml: that object
destructor called

If you call gc() in the scope of the function it doesn't work, probably because the object is still referenced in it:

function test() {
    var test = Test.getObject()
    console.log(test.objectName)
    gc() // doesn't do anything
}

However, if you do it like that:

function test2() {
    test()
    gc()
}

It works, because the garbage collection is triggered after the reference to the object has fallen out of scope:

qml: that object
destructor called
it got destroyed

It seems that when the application exists, it doesn't handle the destroyed() signal, so the echo() slot is never triggered which is probably what misled you into believing the object is leaking unmanaged. I am not sure if this is a product of Qt's design or a bug. The Test object is instantiated on the stack in main() so it should definitely still be "alive" when objects, managed by QML are being destroyed, as should be the event loop it uses, so I would expect to see echo() before the application exits, but it seems this is not the case. But that's not the scope of the question, point of the matter is objects are managed and will be collected when they are no longer referenced and garbage collection is triggered.