Do Qt properties assume objects are pointers and not members? - qt

Let's say I have a C++ object with a member object that I expose to QML:
class X : public QObject
{
Q_OBJECT
};
class Y : public QObject
{
Q_OBJECT
Q_PROPERTY(X* x READ getX CONSTANT)
public:
X* getX(void) { return &x; }
X x;
};
Most of the time this works, but sometimes it will cause crashes. The call stack is quite lengthy but goes like this:
QScopedPointer<QObjectData, QScopedPointerDeleter<QObjectData> >::data
qGetPtrHelper<QScopedPointer<QObjectData>>
QObject::d_func
QObjectPrivate::get
QQmlNotifierEndpoint::disconnect
QQmlNotifierEndpoint::~QQmlNotifierEndpoint
QQmlJavaScriptExpressionGuard::~QQmlJavaScriptExpressionGuard
QRecyclePool<QQmlJavaScriptExpressionGuard, 1024>::Delete
QQmlJavaScriptExpressionGuard::Delete
QQmlJavaScriptExpression::GuardCapture::captureProperty
QQmlEnginePrivate::captureProperty
QV4::QObjectWrapper::getProperty
etc.
If instead I set X to be a pointer:
class Y : public QObject
{
Q_OBJECT
Q_PROPERTY(X* x READ getX CONSTANT)
public:
Y()
{ x = new X; }
X* getX(void) { return x; }
X* x;
};
The crashes go away.
Is this a known restriction of Q_PROPERTY, that if you return a pointer to a QObject, that object is assumed to be a pointer (not a member) and things like deleteLater() might be called?

This crash most likely happens because QML takes ownership of the object c your function returns, and want to delete it at some point late.
In your first example, since x is not allocated from the free store, trying to delete it will crash.
In the second example, the QML engine still tries to delete it, but it does so without problem.
The question to ask is why does the QML engine takes ownership of the object ?
The documentation states in Data Type Conversion Between QML and C++ | Data Ownership :
When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership() with QQmlEngine::CppOwnership specified.
Additionally, the QML engine respects the normal QObject parent ownership semantics of Qt C++ objects, and will not ever take ownership of a QObject instance which already has a parent.
So, in order to remedy to your problem, you could either affect a QObject parent to your x object, or explicitely declaring it having QmlEngine::CppOwnership.

Related

Pass along a Python type to C++ that inherits C++

I have a C++ class RuleSet with a bunch of std::shared_ptr to some rules for a game, most notably to the victory condition, a std::shared_ptr<VictoryRule>. VictoryRule is a abstract C++ type, with one function winners to return the winners of a game. (team_t is a enum, but for simplicity consider it an alias to int.)
class VictoryRule {
public:
//! #brief Check if game is won by a team.
//! #return the teamno of the team that won or team_t::no_team if nobody won.
virtual std::vector<team_t> winners(const Game &game) = 0;
//! #brief Default virtual destructor.
virtual ~VictoryRule() = default;
};
I exposed this class to python with pybind11 to be able to subclass it in python, with a trampoline class.
class PyVictoryRule : public VictoryRule {
public:
std::vector<team_t> winners(const Game &game) override {
PYBIND11_OVERRIDE_PURE(std::vector<team_t>, VictoryRule, winners, game);
}
};
And it's registered with:
pybind11::class_<VictoryRule, PyVictoryRule, std::shared_ptr<VictoryRule>>(
m, "VictoryRule")
.def(pybind11::init())
.def("winners", &VictoryRule::winners, arg("game"));
And I'v reimplemented the Rule in python
class CustomVictoryRule(VictoryRule):
def __init__(self):
VictoryRule.__init__(self)
def winners(self, game):
return [1]
I can instantiate the class in python and call the winners method without any issue.
cvr = CustomVictoryRule()
cvr.winners() # Returns [1]
But I don't want to use the rule directly, I want to store it in the C++ Ruleset.
class RuleSet {
public:
std::shared_ptr<VictoryRule> get_victory_rule();
void register_victory_rule(std::shared_ptr<VictoryRule> victory_rule);
private:
std::unordered_map<CasePawn::Type, std::shared_ptr<Rule>> per_pawn_type_rule_;
std::shared_ptr<VictoryRule> victory_rule_;
};
All methods of RuleSet are registered to pybind11 as-is.
pybind11::class_<RuleSet, std::shared_ptr<RuleSet>>(m, "RuleSet")
.def(pybind11::init())
.def("get_victory_rule", &RuleSet::get_victory_rule)
.def("register_victory_rule", &RuleSet::register_victory_rule,
arg("victory_rule"));
But when I pass along a CustomVictoryRule to RuleSet.register_victory_rule, it loses its dynamic type, and Receive an error for trying to call pure virtual function VictoryRule::winners...
ruleset = RuleSet()
ruleset.register_victory_rule(CustomVictoryRule())
ruleset.get_victory_rule().winners() #fails with RuntimeError: `Tried to call pure virtual function "VictoryRule::winners"`
What should I do so that ruleset.get_victory_rule() return a victory rule with the correct type CustomVictoryRule, so that CustomVictoryRule.winners() is called in that last line of code ?
I found the issue. Instead of doing:
ruleset.register_victory_rule(CustomVictoryRule())
I do:
cvr = CustomVictoryRule()
ruleset.register_victory_rule(cvr)
Then it worked ! So somehow python keeps alive the instance when it's stored into a local variable that's kept alive as long as the ruleset.
Thus, I need to tell pybind11 to keep temporary python objects passed as arguments. Luckily, there's a pybind11::keep_alive decorator to functions to do exactly that. So, binding RuleSet from c++ to python with the following fixes the issue !
pybind11::class_<RuleSet, std::shared_ptr<RuleSet>>(m, "RuleSet")
.def(pybind11::init())
.def("victory_rule", &RuleSet::get_victory_rule)
.def("register_victory_rule", &RuleSet::register_victory_rule,
arg("victory_rule"), keep_alive<1, 2>());

QScopedPointer vs parent

I need some explanation about the next situation. Assume we have the next code:
class MyClass : public QObject
{
public:
MyClass(QObject* parent = nullptr)
{
m_member.reset(new QObject(this));
}
~MyClass(){} override;
private:
QScopedPointer< QObject> m_member;
};
I can't understand if it's safe to pass an object with a parent to QScopedPointer. Might it be any situation when the object is deleted twice by the parent and by the smart pointer and it will lead to a crash?
This is completely safe. Here is what happens when an instance of MyClass gets destroyed:
MyClass's destructor gets called (which does nothing in your example)
all member variables of MyClass get destructed. In your case, QScopedPointer's destructor gets called, which means that the child QObject is deleted. When a QObject is destroyed it is removed from its parent's list of children, so the parent no longer tries to delete this QObject
QObject's destructor for your MyClass instance gets called and it sees no children to delete
You may also want to consider holding your child member QObject by value if there is no reason to allocate it dynamically

Create custom C++ Object in QML and store it in a C++ Model

I have written a customer Class (inheriting from QObject) in C++ and registered it's type successfully with QML. Currently I'm creating objects of this class statically in C++ and storing a pointer to them in a Model which implements QAbstractListModel. In QML in I can access the objects perfectly as items of the Model.
The customObject is a non-visual object.
I'm visualising in another part of the GUI application (QML) the objects in a ListView with a delegate.
However now I would like to create objects from my custom Class dynamically in QML and store them also in the Model. This is where I'm struggling. I hoped I could create a customObject like this:
import com.myProject.myCustomStuff 1.0
...
Button{
id: createObjBtn
text: "create new CustomObj"
onClicked:{
var obj = MyCustomObj;
myObjectManager.addObj(obj); // object holding the implemented QAbstactListModel
console.log(typeof(obj)); // returns [Object object]
console.log(Qt.isQtObject(obj)) // returns false
}
}
I would appreciate your thoughts. Maybe someone knows a way to do this correctly?
Thanks!
Update 1
As requested by Simon-Warta, here is the Constructor implementation of MyCustomObj.
MyCustomObj.cpp
MyCustomObj::MyCustomObj(QObject *parent) : QObject(parent)
{
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
You are confusing the functionality intent of the classes. The QAbstractListModel is intended as a wrapper around a container, yes, you could put the container inside the QAbstractListModel derived class, but you don't really have to, the container can be just about any C++ class, not necessarily even QObject derived, it can be just a QVector<Something> that you can reach from the model via a pointer. Which is good for cases where you have many objects, and not all need to have models at all the time, since those models are pretty heavy.
You don't really need to concern yourself with the ownership, leave that at the C++ side, the same goes for the actual object creation, have a slot called that adds the new object to the container while also using the model's beginInsertRows() and endInsertRows() so that any views will be notified to update efficiently, the new object should also be created in that slot, you can pass any data needed for it from QML, just make sure all data is registered with the Qt meta system so it can work with QVariant for QML-C++ interop.
So it should be something like:
myObjectManager.create(/* any needed data goes here */)
And create() passes eventual data to the C++ side, where you create the object, call beginInsertRows(), add the object to the model's underlying storage, then call endInsertRows() and you are done.
I'd prefer to keep the ownership in the C++ side (and I don't mean explicitly), where I have control over it. Qt kind of sucks in a big way when dealing with object ownership shared between C++ and QML. Ideally, there should be a single shared pointer class that will work across both C++ and QML, so the object is deleted once all reference to it are gone. But that is just not the "Qt way" - the Qt shared pointers do not work with QML, nor do the standard C++ shared pointers, there is actually an entirely different shared reference class for QML, which is not even part of the public API IIRC, very ugly and shortsighted design that only widens the gap between C++ and QML and the associated inconvenience factor.
I don't know if this is the shortest way but this should do for you. I am starting with the basics for those other up-voters.
MyCustomObj.h
class MyCustomObj : public QObject
{
Q_OBJECT
public:
// ...
Q_INVOKABLE void funfunction();
MyCustomObj.cpp
void MyCustomObj::funfunction()
{
qDebug("Fun with QML");
}
main.cpp
qmlRegisterType<MyCustomObj>("com.myProject.myCustomStuff", 1, 0, "MyCustomObj");
app.qml
import com.myProject.myCustomStuff 1.0
ApplicationWindow {
id: mainWindow
Component {
id: myComponent
MyCustomObj {
}
}
Component.onCompleted: {
var obj = myComponent.createObject(mainWindow)
if (!obj) console.error("Error creating object")
console.log(typeof(obj))
console.log(Qt.isQtObject(obj))
obj.funfunction()
}
}
createObject optionally takes properties to be passed to the component.
Storing
Since you are responsible for deleting the objects now, I'd recommend to use shared pointers, such that the objects get destroyed when the List is destroyed.
Your implementation of QAbstactListModel, let's call it MyModel has an adder function like that:
#include <memory> // for std::shared_ptr
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
// ..
Q_INVOKABLE addObj(MyCustomObj* obj)
{
objectlist_.append(std::shared_ptr<MyCustomObj>(obj));
}
private:
QList<std::shared_ptr<MyCustomObj>> objectlist_
}

how to pass qobject as argument from signal to slot in qt connect

My original code passed a QStringList from the signal to the slot and then returned a QList. Everything worked fine but I needed to change both the QStringList and QList into 2 different subclassed QObjects. Since then I have been receiving errors like "synthesized method first required here" or it simply crashes without any error message.
I understand that qt copies all arguments passed in a queued connection and a qobject cannot be copied. So instead of returning a qobject I thought I would create both qobjects prior to emitting the signal. Then I would pass references to each object, modify one of them in the slot function and void the return value. Unfortunately the app still crashes no matter how I code the signal and slot functions. How can I code the signal/slot functions and connect them to either pass both qobjects as arguments or return a qobject?
MyQObject1 obj1("a","b","c");
MyQObject2 obj2();
emit sendSignal(&obj1, &obj2);
// or
MyQObject2 obj2 = emit sendSignal(&obj1);
connect(someObj, SIGNAL(sendSignal(const QObject&)), this, SLOT(receiveSignal(const QObject&)));
The receiveSignal() function does not directly create or modify any qobject. It has to pass the qobjects to another function first which then either modifies obj2 or creates and returns it. Any code examples would be greatly appreciated.
Usually QObject is passed by pointer, not by reference (note that QObject cannot be copied and cannot be passed by value). QObject* is registered as a meta type by default. So creating a signal and a slot with QObject* argument is enough to get them work:
private slots:
void test_slot(QObject* object);
signals:
void test_signal(QObject* object);
Initialization:
connect(this, SIGNAL(test_signal(QObject*)), this, SLOT(test_slot(QObject*)));
Emitting:
QObject* object = new QObject();
emit test_signal(object);
Of course the signal and the slot could be in different classes.

Qt: Q_PROPERTY with pointer and forward declaration for QtScript access

Problem
I am making a project using Q_OBJECT and Q_PROPERTY to access some objects from scripts. I have two problems:
making classes that use forward declarations scriptable
returning a property as pointer
Explanations
1. Why forward declaration?
The class B gets the forward declaration to A, because A needs the complete B type in the header due to the templates. B needs only an incomplete type (A*) in the header, thus the forward declaration is valid.
2. Why returning a pointer?
We cannot return a copy, as we need access to the actual object in the script. We cannot return a reference, as Qt does not allow slots to return references - their type would be ignored, they would only return void*.
Code
Complete code download on pastebin or as ZIP archive or as ZIP archive of minimal example is available, for testing / playing: I needed to split up the files for the forward declaration and for MOC. I added a Makefile to test it. Make deps: g++, moc, Qt.
Important parts
class A; // forward declaration necessary, see explanation above
class B : public QObject {
Q_OBJECT
Q_PROPERTY(A a READ GetA) // <-- ERROR HERE
// ...
public slots:
A* GetA() {
return mA;
}
private:
A* mA;
// ...
}
The error line in the script:
print(bObj.GetA().GetName());
Compile error
This error disappears when I comment out the Q_PROPERTY above.
tmp/B.moc.hpp:95:51: error: invalid use of incomplete type ‘struct A’
tmp/../B.hpp:10:7: error: forward declaration of ‘struct A’
Script exception
When leaving out the Q_PROPERTY and calling the GetA() method as a slot from the script, I get the following exception:
Line 7: "TypeError: cannot call GetA(): unknown return type `A*'
(register the type with qScriptRegisterMetaType())"
When registering A* with qRegisterMetaType<A*>("A*"); this changes to:
Line 7: "TypeError: Result of expression 'bObj.GetA().GetName'
[undefined] is not a function."
That shows that GetA() does not return the A object, or somehow it returns a pointer, but the script cannot dereference it. GetA() then actually returns a QVariant(A*), can this be used somehow?
Questions:
Can I somehow make a Q_PROPERTY from an incomplete type, or how could I avoid the forward declaration?
Can I return a reference in a slot (maybe some tricks, e.g. a class that wraps the pointer and "overrides" the script operator., if something similar exists) or
Can I somehow dereference a QVariant(A*) to A in QtScript?
Your property type is A, not A*, that's why you get very reasonable error.
You should use QScriptValue. Look this code. It works Ok:
class A; // forward declaration necessary, see explanation above
class B : public QObject
{
Q_OBJECT
// Using QScriptValue, made from A instead of A to allow script work correctly with an object
Q_PROPERTY(QScriptValue a READ GetA)
public slots:
QScriptValue GetA() {
//making QScriptValue from A. Type conversion in C style only due to limitation of incomplete type
//In real app it's beter to put defenition of this slot after A's defenition
return static_cast<QScriptEngine*>(parent())->newQObject((QObject*)mA);
}
private:
A* mA;
// ...
public:
//I decided my object will be a child of scriptEngine, but you can take a pointer to it in some other way
B(QScriptEngine * parent);
};
class A: public QObject
{
Q_OBJECT
public slots:
QString GetName() const {return "a name";}
public:
A(QScriptEngine*parent):QObject(parent){}
};
B::B(QScriptEngine *parent):QObject(parent), mA(new A(parent)){}

Resources