3

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
  • You mean you explicitly call `gc()` – sjdowling Dec 05 '14 at 11:38
  • @sjdowling - I usually mean what I mean to mean ;) Do you not? –  Dec 05 '14 at 12:02
  • What type of object is `stack` in QML? Could you add that code or reference to documentation. Thanks – Simon Warta Dec 08 '14 at 15:02
  • 2
    @user3735658 When is the stack cleared? I would not expect gc to have any effect until the reference on the stack is destroyed or am I missing what stack is? – David Woo Dec 09 '14 at 01:15
  • 1
    @user3735658: does your `stack` still have a reference to the object? Could you please paste a [_complete_ test case](http://stackoverflow.com/help/mcve)? You would increase your chance to get answers. – László Papp Dec 10 '14 at 12:37
  • @lpapp - even without using the stack at all the returned C++ object is never collected. It is not collected after the function ends, it is not collected if assigned to a property of an object which is destroyed. –  Dec 10 '14 at 17:34
  • Just return a `QObject` to QML, output its objectName just for the sake of using it and see if it is collected after it falls out of scope. Because for me it is not... –  Dec 10 '14 at 17:36
  • @user3735658: `"property QtObject: CXTProp.getCppQObjectStar()"` is invalid qml, or you can say incomplete and the other example is missing the `gc` call. You need to call `gc` after the function scope is left because inside the scope the javascript stack may still be using it. That is also similar to `destroy()` which is basically `deleteLater()`. Could you please provide a self-contained example that reproduces the bug you are trying to show? – László Papp Dec 11 '14 at 12:45

1 Answers1

4

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.

dtech
  • 47,916
  • 17
  • 112
  • 190