QML object access through model crashes
-
Hi all -
My app is doing something rather strange - upon entering a QML component the first time, everything works, but upon entering it a second time, it crashes. I can't tell exactly where it's crashing because of an RTTI issue, but I think it has to do with the final line in this passage:
class Outcome : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(bool isRunning READ isRunning WRITE setIsRunning NOTIFY isRunningChanged FINAL) bool isRunning() { return m_isRunning; } bool setIsRunning(bool running); ... } Outcome* OutcomeModel::getOutcome(const QUuid &uuid) { auto outcome {std::make_shared<Outcome>(this)}; const auto i { getIndex(uuid) }; if (i == NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; } else { outcome = m_list.at(i); } return outcome.get(); } // qml text: "running: " + outcomeModel.getOutcome(modelData).isRunning
I've used other properties instead of my isRunning, and the behavior is the same. Is there something wrong with my getOutcome() routine, or the way I'm calling it from QML?
Thanks...
-
@GrecKo from my updateModel() function:
std::shared_ptr<Outcome> pOutcome = nullptr; // determine whether the outcome in this message already exists in list. const auto listIndex { getIndex(uuid) }; if (listIndex == NgaUI::NOT_IN_LIST) { // will append to list below. pOutcome = std::make_shared<Outcome>(this); } else { pOutcome = m_list.at(listIndex); } ... if (listIndex == NgaUI::NOT_IN_LIST) { beginInsertRows(QModelIndex(), m_list.size(), m_list.size()); m_list.append(pOutcome); endInsertRows();
EDIT:
Regarding who they're shared with, I do create an instance in QML, using my getOutcome() function, but this works with a raw pointer, so I don't think it's sharing the smart pointer, right?
-
Hi all -
My app is doing something rather strange - upon entering a QML component the first time, everything works, but upon entering it a second time, it crashes. I can't tell exactly where it's crashing because of an RTTI issue, but I think it has to do with the final line in this passage:
class Outcome : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(bool isRunning READ isRunning WRITE setIsRunning NOTIFY isRunningChanged FINAL) bool isRunning() { return m_isRunning; } bool setIsRunning(bool running); ... } Outcome* OutcomeModel::getOutcome(const QUuid &uuid) { auto outcome {std::make_shared<Outcome>(this)}; const auto i { getIndex(uuid) }; if (i == NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; } else { outcome = m_list.at(i); } return outcome.get(); } // qml text: "running: " + outcomeModel.getOutcome(modelData).isRunning
I've used other properties instead of my isRunning, and the behavior is the same. Is there something wrong with my getOutcome() routine, or the way I'm calling it from QML?
Thanks...
-
Hi all -
My app is doing something rather strange - upon entering a QML component the first time, everything works, but upon entering it a second time, it crashes. I can't tell exactly where it's crashing because of an RTTI issue, but I think it has to do with the final line in this passage:
class Outcome : public QObject { Q_OBJECT QML_ELEMENT Q_PROPERTY(bool isRunning READ isRunning WRITE setIsRunning NOTIFY isRunningChanged FINAL) bool isRunning() { return m_isRunning; } bool setIsRunning(bool running); ... } Outcome* OutcomeModel::getOutcome(const QUuid &uuid) { auto outcome {std::make_shared<Outcome>(this)}; const auto i { getIndex(uuid) }; if (i == NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; } else { outcome = m_list.at(i); } return outcome.get(); } // qml text: "running: " + outcomeModel.getOutcome(modelData).isRunning
I've used other properties instead of my isRunning, and the behavior is the same. Is there something wrong with my getOutcome() routine, or the way I'm calling it from QML?
Thanks...
@mzimmers said in QML object access through model crashes:
Hi all -
My app is doing something rather strange - upon entering a QML component
What does
entering
mean?Outcome* OutcomeModel::getOutcome(const QUuid &uuid) { auto outcome {std::make_shared<Outcome>(this)}; const auto i { getIndex(uuid) }; if (i == NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; } else { outcome = m_list.at(i); } return outcome.get(); }
This looks extremely suspicious. The QML engine doesn't know about shared_ptr, and the get() is stripping that information anyway. The engine presumes ownership of created objects unless told otherwise. That means that when the object goes out of scope, the engine will eventually invoke the Outcome destructor. Holding a shared pointer to it elsewhere won't prevent that.
If I understand the
else
section, under some circumstance the OutcomeModel factory will attempt to reuse a previously created object that may have been deleted by the engine. -
@mzimmers said in QML object access through model crashes:
Hi all -
My app is doing something rather strange - upon entering a QML component
What does
entering
mean?Outcome* OutcomeModel::getOutcome(const QUuid &uuid) { auto outcome {std::make_shared<Outcome>(this)}; const auto i { getIndex(uuid) }; if (i == NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; } else { outcome = m_list.at(i); } return outcome.get(); }
This looks extremely suspicious. The QML engine doesn't know about shared_ptr, and the get() is stripping that information anyway. The engine presumes ownership of created objects unless told otherwise. That means that when the object goes out of scope, the engine will eventually invoke the Outcome destructor. Holding a shared pointer to it elsewhere won't prevent that.
If I understand the
else
section, under some circumstance the OutcomeModel factory will attempt to reuse a previously created object that may have been deleted by the engine.@jeremy_k said in QML object access through model crashes:
What does entering mean?
I mean when a component is loaded.
In this case, the list itself is "good" -- that is, there have been only additions to it, no deletions. My list is declared like this:
typedef QList<std::shared_ptr<Outcome>> OutcomeList; OutcomeList m_list;
I don't really need to maintain a list of pointers; my list could be the actual objects, but then code like this:
outcome = m_list.at(listIndex);
becomes problematic because, as a QObject-derived class, Outcome has no copy c'tor. So, I'm not sure what the best way to go is here.
-
@mzimmers you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call. I guess you are supposed to return outcome or make outcome a local variable in OutcomeModel
@JoeCFD said in QML object access through model crashes:
you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call.
But isn't the pointer itself still valid (meaning it's pointing to the correct item in the list)?
-
@JoeCFD said in QML object access through model crashes:
you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call.
But isn't the pointer itself still valid (meaning it's pointing to the correct item in the list)?
-
@JoeCFD said in QML object access through model crashes:
you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call.
But isn't the pointer itself still valid (meaning it's pointing to the correct item in the list)?
@mzimmers said in QML object access through model crashes:
@JoeCFD said in QML object access through model crashes:
you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call.
But isn't the pointer itself still valid (meaning it's pointing to the correct item in the list)?
Add some logging to the destructor to see when the object is destroyed.
-
@JoeCFD said in QML object access through model crashes:
you are returning the raw pointer of outcome from this func. And outcome is destroyed after this func call.
But isn't the pointer itself still valid (meaning it's pointing to the correct item in the list)?
Also, unless you're fan of pain and suffering don't hold
QObject
s in shared pointers. You're basically saying that the object owns itself (semantically) and have no guarantee when this object is going to be freed. This in turn may cause you to execute a slot on an object that's getting/got deleted.If you want to own a
QObject
usestd::unique_ptr
/QScopedPointer
, if you're going to hold a weak reference to the object (i.e. to hold a pointer without owning the actual object) then useQPointer
. -
Also, unless you're fan of pain and suffering don't hold
QObject
s in shared pointers. You're basically saying that the object owns itself (semantically) and have no guarantee when this object is going to be freed. This in turn may cause you to execute a slot on an object that's getting/got deleted.If you want to own a
QObject
usestd::unique_ptr
/QScopedPointer
, if you're going to hold a weak reference to the object (i.e. to hold a pointer without owning the actual object) then useQPointer
.I added a member to my OutcomeModel:
Outcome *m_outcome;
and modified my get routine:
Outcome *OutcomeModel::getOutcome(const QUuid &uuid) { bool found = false; m_outcome = nullptr; for (const auto &o: m_list) { if (o->uuid() == uuid) { m_outcome = o.get(); found = true; break; } } if (!found) { m_outcome = m_list.at(0).get(); } return m_outcome; }
(I realize this isn't the desired ultimate behavior, but I wanted to try to ensure that m_outcome would always point to something valid - the list will always contain at least one item).
I'm getting the same behavior. Obviously I'm missing something, but I don't see what it is.
-
I added a member to my OutcomeModel:
Outcome *m_outcome;
and modified my get routine:
Outcome *OutcomeModel::getOutcome(const QUuid &uuid) { bool found = false; m_outcome = nullptr; for (const auto &o: m_list) { if (o->uuid() == uuid) { m_outcome = o.get(); found = true; break; } } if (!found) { m_outcome = m_list.at(0).get(); } return m_outcome; }
(I realize this isn't the desired ultimate behavior, but I wanted to try to ensure that m_outcome would always point to something valid - the list will always contain at least one item).
I'm getting the same behavior. Obviously I'm missing something, but I don't see what it is.
As usual, if we are tracking a segfault here, do provide a stack trace.
-
@kshegunov I can't - the error occurs somewhere within QObject and there's no RTTI information for it.
BTW: I'm not ignoring your suggestion about shared_ptr; I just want to get this figured out first.
-
@kshegunov I can't - the error occurs somewhere within QObject and there's no RTTI information for it.
BTW: I'm not ignoring your suggestion about shared_ptr; I just want to get this figured out first.
@mzimmers said in QML object access through model crashes:
@kshegunov I can't - the error occurs somewhere within QObject and there's no RTTI information for it.
I know you mentioned this elsewhere, but this ought not stop you getting a stack trace from a seg fault. It might stop you seeing the internals of a
QObject
, but are you saying this is somehow an "error" in the debugger which prevents you accessing a stack trace? Even if it does inside aQObject
, you should still see information about where it was in your or Qt code. -
@kshegunov I can't - the error occurs somewhere within QObject and there's no RTTI information for it.
BTW: I'm not ignoring your suggestion about shared_ptr; I just want to get this figured out first.
@mzimmers said in QML object access through model crashes:
BTW: I'm not ignoring your suggestion about shared_ptr; I just want to get this figured out first.
Substitute your
shared_ptr
withQPointer
and when you fill in that list usenew
. At the point of crash if you are seeing dereferencing anullptr
, then something's not right with the ownership; that is to say something (probably QML) took ownership of the objects and freed them and you were left with dangling pointers all over the place.PS.
You should almost never useshared_ptr::get
, which erases the notion of the control block and who/when this object is going to be culled. -
@mzimmers said in QML object access through model crashes:
BTW: I'm not ignoring your suggestion about shared_ptr; I just want to get this figured out first.
Substitute your
shared_ptr
withQPointer
and when you fill in that list usenew
. At the point of crash if you are seeing dereferencing anullptr
, then something's not right with the ownership; that is to say something (probably QML) took ownership of the objects and freed them and you were left with dangling pointers all over the place.PS.
You should almost never useshared_ptr::get
, which erases the notion of the control block and who/when this object is going to be culled.Thanks to your suggestions, I think I'm making progress. In my OutcomeModel, I've eliminated the Outcome element, and done the following:
typedef QPointer<Outcome> OutcomePtr; typedef QList<OutcomePtr> OutcomeList; OutcomePtr OutcomeModel::getOutcome(const QUuid &uuid) { // auto outcome {std::make_shared<Outcome>(this)}; OutcomePtr outcome { new Outcome }; const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; // I never see this warning. } else { delete outcome; outcome = m_list.at(i); } return outcome.data(); }
I'm no longer getting segmentation faults, so I think we may have solved that problem. So, now the remaining issue is how to use the QPointer to access the Outcome properties from my QML? I tried this:
ListView { model: outcomeList // a list of UUIDs delegate: RowLayout { Label { text: "running: " + outcomeModel.getOutcome(modelData).isRunning
but the Label shows as undefined. I've verified that the getOutcome() routine seems to be returning good data. Any ideas what I'm doing wrong?
Thanks...
-
Thanks to your suggestions, I think I'm making progress. In my OutcomeModel, I've eliminated the Outcome element, and done the following:
typedef QPointer<Outcome> OutcomePtr; typedef QList<OutcomePtr> OutcomeList; OutcomePtr OutcomeModel::getOutcome(const QUuid &uuid) { // auto outcome {std::make_shared<Outcome>(this)}; OutcomePtr outcome { new Outcome }; const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; // I never see this warning. } else { delete outcome; outcome = m_list.at(i); } return outcome.data(); }
I'm no longer getting segmentation faults, so I think we may have solved that problem. So, now the remaining issue is how to use the QPointer to access the Outcome properties from my QML? I tried this:
ListView { model: outcomeList // a list of UUIDs delegate: RowLayout { Label { text: "running: " + outcomeModel.getOutcome(modelData).isRunning
but the Label shows as undefined. I've verified that the getOutcome() routine seems to be returning good data. Any ideas what I'm doing wrong?
Thanks...
-
@mzimmers delegate in ListView is a component from here:
https://doc.qt.io/qt-6/qml-qtquick-listview.html#delegate-prop
Is RowLayout a component?@JoeCFD if I understand your point, I need to do this instead:
ListView { model: outcomeList delegate: rowComponent Component { id: rowComponent RowLayout { Label { id: runningLabel text: "running: " + outcomeModel.getOutcome(modelData).isRunning
Is this what you were getting at? The behavior seems the same.
Thanks...
-
Thanks to your suggestions, I think I'm making progress. In my OutcomeModel, I've eliminated the Outcome element, and done the following:
typedef QPointer<Outcome> OutcomePtr; typedef QList<OutcomePtr> OutcomeList; OutcomePtr OutcomeModel::getOutcome(const QUuid &uuid) { // auto outcome {std::make_shared<Outcome>(this)}; OutcomePtr outcome { new Outcome }; const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; // I never see this warning. } else { delete outcome; outcome = m_list.at(i); } return outcome.data(); }
I'm no longer getting segmentation faults, so I think we may have solved that problem. So, now the remaining issue is how to use the QPointer to access the Outcome properties from my QML? I tried this:
ListView { model: outcomeList // a list of UUIDs delegate: RowLayout { Label { text: "running: " + outcomeModel.getOutcome(modelData).isRunning
but the Label shows as undefined. I've verified that the getOutcome() routine seems to be returning good data. Any ideas what I'm doing wrong?
Thanks...
Any ideas what I'm doing wrong?
To be blunt I'd say having trouble explaining what your issue is and blindly following strangers advices.
QPointer is an observing pointer. Don't use it to keep ownership.
shared_ptr was fine, unique_ptr may have been better. raw pointers could also be used if using the QObject parent ownership system, it would require to manually delete the object on removal though.Your unusual code rules do not help you there (the "always init your variables at the start of a function" and "only one return").
Your getOutcome function is leaking (when not finding the uuid, doing unnecessary temporary allocation when finding it).
the Label shows as undefined
Is the text "undefined", "running: undefined", something else?
what doesoutcomeModel.getOutcome(modelData)
returns?The source issue of your problem was most likely that your object has no QObject::parent, thus the QML engine taking ownership of it when it access it from your Q_INVOKABLE.
https://doc.qt.io/qt-6/qtqml-cppintegration-data.html#data-ownership
I'd go back to using shared_ptr or unique_ptr and making sure to set a parent to your contained objects so the QML engine don't take ownership of it.
Keep in mind I'm also an internet stranger, so don't apply blindly what I'm claiming.
-
Any ideas what I'm doing wrong?
To be blunt I'd say having trouble explaining what your issue is and blindly following strangers advices.
QPointer is an observing pointer. Don't use it to keep ownership.
shared_ptr was fine, unique_ptr may have been better. raw pointers could also be used if using the QObject parent ownership system, it would require to manually delete the object on removal though.Your unusual code rules do not help you there (the "always init your variables at the start of a function" and "only one return").
Your getOutcome function is leaking (when not finding the uuid, doing unnecessary temporary allocation when finding it).
the Label shows as undefined
Is the text "undefined", "running: undefined", something else?
what doesoutcomeModel.getOutcome(modelData)
returns?The source issue of your problem was most likely that your object has no QObject::parent, thus the QML engine taking ownership of it when it access it from your Q_INVOKABLE.
https://doc.qt.io/qt-6/qtqml-cppintegration-data.html#data-ownership
I'd go back to using shared_ptr or unique_ptr and making sure to set a parent to your contained objects so the QML engine don't take ownership of it.
Keep in mind I'm also an internet stranger, so don't apply blindly what I'm claiming.
@GrecKo points taken.
@GrecKo said in QML object access through model crashes:
Your unusual code rules do not help you there (the "always init your variables at the start of a function" and "only one return").
Your getOutcome function is leaking (when not finding the uuid, doing unnecessary temporary allocation when finding it).Is this better? EDIT: I can't do this with unique_ptr; the second return statement is invalid.
OutcomePtr OutcomeModel::getOutcome(const QUuid &uuid) { const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; return nullptr; } else { return m_list.at(i); } }
@GrecKo said in QML object access through model crashes:
Is the text "undefined", "running: undefined", something else?
"running: undefined"
@GrecKo said in QML object access through model crashes:
what does outcomeModel.getOutcome(modelData) returns?
I also notice that the address of outcome doesn't match the address of m_list[2], which I find somewhat strange (if these were old-fashioned pointers, it should, I believe).
EDIT: this screenshot might be more helpful (this was using a shared_ptr):
I do notice that now the addresses of outcome and m_list[2] agree, so I guess this is progress. Still having the "undefined" issue, though. -
@GrecKo points taken.
@GrecKo said in QML object access through model crashes:
Your unusual code rules do not help you there (the "always init your variables at the start of a function" and "only one return").
Your getOutcome function is leaking (when not finding the uuid, doing unnecessary temporary allocation when finding it).Is this better? EDIT: I can't do this with unique_ptr; the second return statement is invalid.
OutcomePtr OutcomeModel::getOutcome(const QUuid &uuid) { const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { qWarning() << __PRETTY_FUNCTION__ << "uuid not found in list."; return nullptr; } else { return m_list.at(i); } }
@GrecKo said in QML object access through model crashes:
Is the text "undefined", "running: undefined", something else?
"running: undefined"
@GrecKo said in QML object access through model crashes:
what does outcomeModel.getOutcome(modelData) returns?
I also notice that the address of outcome doesn't match the address of m_list[2], which I find somewhat strange (if these were old-fashioned pointers, it should, I believe).
EDIT: this screenshot might be more helpful (this was using a shared_ptr):
I do notice that now the addresses of outcome and m_list[2] agree, so I guess this is progress. Still having the "undefined" issue, though.Update: I've got something now that works (doesn't crash and properly updates the QML). Here's the C++:
typedef std::shared_ptr<Outcome> OutcomePtr; typedef QList<OutcomePtr> OutcomeList; Outcome *OutcomeModel::getOutcome(const QUuid &uuid) { const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { return nullptr; } else { OutcomePtr outcome(m_list.at(i)); return outcome.get(); } }
And the QML:
ListView { model: outcomeList delegate: rowComponent Component { id: rowComponent RowLayout { property Outcome outcome: outcomeModel.getOutcome(modelData) Label { text: "running: " + outcome.isRunning }
So, I have 2 follow up questions:
- if I were to use unique_ptrs instead of shared_ptrs, how do I code this line? I can't understand from the docs.
OutcomePtr outcome(m_list.at(i));
- any other comments/room for improvement that anyone can see?
Thanks to everyone for the help on this...
-
Update: I've got something now that works (doesn't crash and properly updates the QML). Here's the C++:
typedef std::shared_ptr<Outcome> OutcomePtr; typedef QList<OutcomePtr> OutcomeList; Outcome *OutcomeModel::getOutcome(const QUuid &uuid) { const auto i { getIndex(uuid) }; if (i == NgaUI::NOT_IN_LIST) { return nullptr; } else { OutcomePtr outcome(m_list.at(i)); return outcome.get(); } }
And the QML:
ListView { model: outcomeList delegate: rowComponent Component { id: rowComponent RowLayout { property Outcome outcome: outcomeModel.getOutcome(modelData) Label { text: "running: " + outcome.isRunning }
So, I have 2 follow up questions:
- if I were to use unique_ptrs instead of shared_ptrs, how do I code this line? I can't understand from the docs.
OutcomePtr outcome(m_list.at(i));
- any other comments/room for improvement that anyone can see?
Thanks to everyone for the help on this...
@GrecKo said in QML object access through model crashes:
To be blunt I'd say having trouble explaining what your issue is and blindly following strangers advices.
Hey, I take pride in my internet stranger's random advices.
QPointer is an observing pointer. Don't use it to keep ownership.
As already stated ...
@mzimmers said in QML object access through model crashes:
if I were to use unique_ptrs instead of shared_ptrs, how do I code this line? I can't understand from the docs.
You don't.
unique_ptr
/QScopedPointer
is an owning wrapper. You'd return the underlying raw pointer instead (which you already do withshared_ptr
).
I believe this is what you're looking for: https://en.cppreference.com/w/cpp/memory/unique_ptr/get