0

In my app I have Q_INVOKABLE function that returns a naked pointer to a QObject owned by std::shared_ptr:

namespace tradeclient
{

    class OrderModel : public QObject
    {
        Q_OBJECT

    public:

        Q_PROPERTY(QString marketId READ marketId CONSTANT)
        Q_PROPERTY(quint64 id READ id CONSTANT)

    signals:

        void failed(qint64 code, QString message);

        ...
    };

    using OrderPtr = std::shared_ptr<OrderModel>;

    class MarketModel : public QObject
    {
        Q_OBJECT

    public:

        Q_INVOKABLE tradeclient::OrderModel* createLimitOrder()
        {
            return m_orders.front().get();
        }

    private:

        //In my app I initialize OrderModel-s and add them so some container and set C++ ownership.
        std::vector<OrderPtr> m_orders;
    };

} //namespace tradeclient

Q_DECLARE_METATYPE(tradeclient::OrderPtr)

and I am trying to invent a way to use std::shared_ptr from both C++ and QML.

I did various experimentation with Q_DECLARE_SMART_POINTER_METATYPE and came to the conclusion that is useless in my app because it does not expose wrapped object properties to QML.

As the next experiment I tried to declare a gadget containing std::shared_ptr as a member:

namespace tradeclient
{
    class OrderGadget
    {
        Q_GADGET

        Q_PROPERTY(tradeclient::OrderModel* p READ pointer);

    public:

        OrderGadget() = default;

        OrderGadget(OrderPtr p) : m_p(std::move(p)) {}

    private:

        tradeclient::OrderModel* pointer()
        {
            return m_p.get();
        }

        OrderPtr m_p;
    };
}

Q_DECLARE_METATYPE(tradeclient::OrderGadget)

return it from createLimitOrder() as follows:

namespace tradeclient
{
    using OrderPtr = std::shared_ptr<OrderModel>;

    class MarketModel : public QObject
    {
        Q_OBJECT

    public:

        Q_INVOKABLE tradeclient::OrderGadget createLimitOrder()
        {
            return m_orders.front();
        }

    private:

        //Leave default ownership.
        std::vector<OrderPtr> m_orders;
    };

}

and access OrderModel object via p property in QML:

var shared_order = market.createLimitOrder()

var order = shared_order.p

if (order)
{
    console.log("Qml", "Limit order type: %1, value: %2, id: %3".arg(typeof(order)).arg(JSON.stringify(order)).arg(order.marketId))
    order.failed.connect((code, message) => { window.showFading("%1 order has failed: %2".arg(shared_order.p.marketId).arg(message))})
}
else
    console.log("Qml", "The order is not defined.");

This QML code prints object properties:

Limit order type: object, value: {"objectName":"","marketId":"BTCUSDT","id":0,"side":0 ...

and connects to failed signal.

But it is not clear enough is this code correct. Is it possible that GC will delete order variable after the order object already destroyed by std::shared_ptr?

What is the lifetime of failed handler function that refers shared_order?

Dmitriano
  • 1,878
  • 13
  • 29
  • so what if gc deletes anything? shared_ptr instance is the one who ultimately deletes the object it owns. You're troubling yourself too much over presentation and too little with what you are actually trying to accomplish. – user1095108 Sep 01 '22 at 12:23
  • @user1095108 You do not trouble yourself and add useless comments. – Dmitriano Sep 02 '22 at 00:14
  • Just to make sure you understand: `QObject` instances commonly have a parent. Also Qt model items and such often have another object owning them. If that parent or owner ever ends up deleting the object, it has no way to release a `std::shared_ptr` elsewhere (other way should usually be ok, the object will inform its parent/owner that it got deleted by the `std::shared_ptr`). Are you sure you want to mix `QObject` and `std::shared_ptr`? – hyde Sep 02 '22 at 05:07
  • @hyde Yes, all the objects owned by `std::shared_ptr` are parentless in my app. – Dmitriano Sep 02 '22 at 20:08

1 Answers1

1

I'm afraid you can't do this directly. Here's a Qt bug report about the feature:
https://bugreports.qt.io/browse/QTBUG-43080

One work-around seems to be to wrap the std::shared_ptr in a QObject and expose the original wrapped object as a QObject* property of that wrapper.

Here's link to comment under above bug report, which contains sample code you can adapt for std::shared_ptr.


In case the link breaks, the basic idea is to provide this property:

class SmartPointerWrapper : public QObject {
    Q_OBJECT
    Q_PROPERTY(QObject* data READ data NOTIFY dataChanged)
    ...

The property then needs to be implemented to give access to the pointer, which is internally stored in the std::shared_ptr.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • Why `Q_OBJECT`, but not `Q_GADGET`? – Dmitriano Sep 02 '22 at 20:14
  • 1
    @Dmitriano Q_GADGET doesn't support notify signal, I believe. Might, or might not be an issue. Bigger issue might be, if Q_GADGET is deleted by the smart pointer, it can't notify anybody. Not sure if these matter for your use case, so you can try it... – hyde Sep 03 '22 at 04:45
  • Assume I implemented `Q_GADGET`. Should I avoid assigning naked pointer to a variable in QML as I do in my code example `var order = shared_order.p`? When `order` vairiable is garbage collected `OrderModel` is destroyed resulting in dangling `std::shared_ptr`, right? – Dmitriano Sep 03 '22 at 09:00
  • `p` is a QObject created in C++? Just give it a parent (like `qApp` if you can't think of anything else) and it shouldn't get deleted by GC. Maybe, not super familiar with this stuff, I generally just use QObject ownership model for QObjects, not smart pointers. – hyde Sep 03 '22 at 09:19
  • Yes, `p` is a property of `OrderGadget` that contains an object created in C++ with the default ownership. I need the object to be destroyed when all `OrderGadget` instancies are garbage collected in QML, so the the object should be parentless. The question is what will happen if I do `var order = shared_order.p`? – Dmitriano Sep 03 '22 at 10:13
  • 1
    @Dmitriano Maybe this will help: https://stackoverflow.com/questions/33792876/qml-garbage-collection-deletes-objects-still-in-use – hyde Sep 03 '22 at 11:24
  • Probably if an object is obtained from a property like `p` it is not garbage collected even I assign a variable with `var order = shared_order.p`. Need to check this. If so, my `OrderGadget` can be usable in QML. The only thing that I still do not understand is what the lifetime of `order.failed.connect` signal handler. – Dmitriano Sep 03 '22 at 15:11
  • As far as I can guess `order.failed.connect` signal handler it will never be disconnected until `order` is destroyed and I have a cyclic dependency because it refers `shared_order` (because there is no way to access `sender()` in QML) that will never be destroyed. – Dmitriano Sep 03 '22 at 15:18