PDA

View Full Version : Const-Correctness With Q_PROPERTY



Sanuden
15th November 2010, 14:45
I'm trying to develop a class that will be used in a reflection driven application and I ran into an interesting issue that I'm hoping there is a solution to. Let's say I have a class Dummy that has a few properties, one of which is a pointer to another Dummy object:


class Dummy : public QObject
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId)
Q_PROPERTY(QObject * child READ child)

int myId;
DummyObject *myChild;

public:

Dummy(int id)
: myId(id), myChild(0)
{
if (myId == 0)
{
myChild = new Dummy(myId + 1);
}
}

~Dummy()
{
delete myChild;
}

int id() const
{
return myId;
}

void setId(int id)
{
myId = id;
}

QObject * child()
{
return myChild;
}

};


And then lets say I create a new Dummy object with id = 0 (so it has one child with id = 1), if I pass the parent Dummy object to a function that takes a constant reference to a QObject (or a constant pointer for that matter), how can I guarantee const-correctness on the child function? I tried the obvious: Q_PROPERTY(const QObject * child READ child) and then making const QObject * child() const (which is really how I want this function to look like), but then the moc complains about: "Unable to handle unregistered datatype 'const QObject*'". I just can't figure out how to make it so that if I pass a constant reference or pointer to a Dummy object that the "child" function (through the qt property system) will expose a constant reference or pointer (to ensure const-correctness).

high_flyer
15th November 2010, 15:12
The property name and type and the READ function are required. The type can be any type supported by QVariant, or it can be a user-defined type.
Now look at enum QVariant::Type.
QObject * is not there.

Then:

Custom types used by properties need to be registered using the Q_DECLARE_METATYPE() macro so that their values can be stored in QVariant objects. This makes them suitable for use with both static properties declared using the Q_PROPERTY() macro in class definitions and dynamic properties created at run-time.

have a look at QVariant qVariantFromValue ( const T & value );.

One easy way would be to typedef a long as pointer type and convert it in your class back to pointer again.

wysota
15th November 2010, 15:34
I'd say that trying to have a static property that holds QObject pointers is a bad idea without even dwelling about whether it works or not. Qt has another facility for holding hierarchies of objects that you should be using here. If you want to hold some reference to the child object then have that property contain a unique id to the child object that can be used to retrieve the real object pointer for the id.

Something like this should work:

class MyObject : public QObject {
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId)
Q_PROPERTY(int childId READ childId WRITE setChildId)
public:
// ...
void setId(int id) {
if(m_id == id) return;
if(ObjectManager::instance()->hasId(m_id)) {
ObjectManager::instance()->unregister(m_id);
}
if(ObjectManager::instance()->register(id, this)){
m_id = id;
} else {
qWarning("Id %d already taken. Operation failed.", id);
}
}
void setChildId(int id) {
if(id == m_childId) return;
if(!ObjectManager::instance()->hasId(id)){
qWarning("No object with id %d exists. Operation failed.", id);
return;
}
m_childId = id;
}
};

// ...

QObject* ObjectManager::objectById(int id) const {
return m_objects.value(id, 0);
}

provided ObjectManager is a singleton handling registration of objects in the system.

Sanuden
15th November 2010, 15:48
Yeah, I mean I can make this work by doing something like:


// Split property into tokens and push into queue
QStringList tokens = myProperty.split(QChar('.'));
QQueue<QString> queue;
foreach (QString str, tokens)
{
queue.enqueue(str);
}

// Reference object pointer and resolve base properties
const QObject *obj = &object;
while (queue.size() > 1)
{
QString token = queue.dequeue();

if (obj->property(token.toLocal8Bit().constData()).canConve rt<QObject *>())
{
qDebug() << token;
QObject *obj2 = obj->property(token.toLocal8Bit().constData()).value<QObject *>(); // I don't want this cast to be allowed
obj2->setProperty("id", -2); // because this shouldn't be possible
obj = obj->property(token.toLocal8Bit().constData()).value<QObject *>();
}
}

QString prop = queue.dequeue();
qDebug() << prop;
qDebug() << obj->property(prop.toLocal8Bit().constData()).toInt();


So in this block of code (which I've been working with for testing) I am splitting the property string for the supplied const Dummy reference "object" (in this case myProperty is equal to "child.id") into the respective components and then resolving them on the base object. The output for this code is what I expect:



"child"
"id"
1 (or -2 if the line that shouldn't be allowed is there)


However, I don't want the two lines that essentially pull a QObject * from the property "child" to be permissible in this function (since obj is a const QObject *). I don't know what to do to the Q_PROPERTY or child function to prevent this.

Added after 7 minutes:

wysota:

Yeah, that was another route I was considering, however, maybe if I put this into context you'll understand what I'm trying to do. Basically, I'm writing a generic logical expression interpreter so that if you want to know if the value of myObject1->propertyA()->propertyB()->propertyC() is greater than 4 (or any value) where myObject1 has a function that exposes another QObject through propertyA(), which then exposes another QObject through propertyB() which has a function called propertyC() that returns an int, you could pass in myObject1 with a property qualitfier of "propertyA.propertyB.propertyC" and a value of 4 (and comparison type), and it could use reflection to dig into the properties to find the final value.

The goal is to be able to pass any generic object to this type of logic operator and be able to reflect on the desired comparison value (which will always be a QVariant).

wysota
15th November 2010, 16:30
Yeah, that was another route I was considering, however, maybe if I put this into context you'll understand what I'm trying to do. Basically, I'm writing a generic logical expression interpreter so that if you want to know if the value of myObject1->propertyA()->propertyB()->propertyC() is greater than 4 (or any value) where myObject1 has a function that exposes another QObject through propertyA(), which then exposes another QObject through propertyB() which has a function called propertyC() that returns an int, you could pass in myObject1 with a property qualitfier of "propertyA.propertyB.propertyC" and a value of 4 (and comparison type), and it could use reflection to dig into the properties to find the final value.

The goal is to be able to pass any generic object to this type of logic operator and be able to reflect on the desired comparison value (which will always be a QVariant).

Well, the problem is this pseudocode immediately crashes your application:


QObject *o1 = new QObject;
QObject *o2 = new QObject;
o1->setProperty("child", o2);
delete o2;
o1->property("child")->anything();
and you don't even know why (value returned by o1->property() is not null).

What you want to achieve is essentially this (using object names instead of properties):

QVariant evaluate(QObject *obj, const QString &expression){
QStringList path = expression.split(".");
QString method = path.last();
path.removeLast();
foreach(QString subobj, path){
obj = obj->findChild<QObject*>(subobj);
if(!obj) return QVariant(); // invalid path
}
// here we have the final object to call the method on.
QMetaMethod meth = obj->metaObject()->method(obj->metaObject()->indexOfMethod(qPrintable(method));
// for string return type:
QString ret;
if(meth.invoke(obj, Q_RETURN_ARG(ret))) {
return ret;
} else {
return QVariant(); // call failed
}
}

You can do the same with a custom QObject subclass and a custom method if you don't want to use the built-in parent-child relationship. The thing is you should have a single place to control existance of the objects. It will not let you have constness guarantee but I doubt you can have it anyway (you can always cheat the compiler with e.g. a const_cast).

Sanuden
15th November 2010, 16:53
Yeah, I see where you are going with this. Ideally I would like to have the "child" functions (the ones returning QObjects) to return references to QObjects (thus preventing them from being null, but obviously not preventing the type of failure you indicated). However, at least that way it is probably not a reasonable assumption when you are designing a class that if you have a function return a reference to an object that the reference be valid. That being said, I never considered using the built-in parent-child relationship that Qt exposes. I'm not sure if I'm a tremendous fan of using Qt's built in mechanism (since this is trying to be a generic framework for this type of logic), but at the same time (like you pointed out), it would be easy enough to create my own abstract class to implement this type of logic.

This begs one final question, is it common practice with QObjects to set up the parent-child relationship for something that just owns another class (like the Dummy class in my example)? I know for Gui concepts it makes a lot of sense (because of the memory management), but for general classes, should this be expected?

Thanks again.

wysota
15th November 2010, 18:18
As for most questions the answer is "it depends". Sometimes a parent-child relationship is established (usually if the child needs to access its parent) but in some cases a dynamic property is used (if the helper object should be hidden (or rather obscured) from the outside world). And in some cases there is a dedicated method for registering an object, especially if it can work with many other objects.

What is the general goal of implementing the mechanism you are trying to create?

Sanuden
15th November 2010, 18:29
The general goal is the following:

Be able to create a generic "Logical Expression" mechanism so that one may first initialize a logical expression, consisting of multiple "Logical Conditions" and then provide an appropriate QObject to be evaluated by the expression on the basis of being true or false.

For example, say I have a class called Car which has a property that is another class called Engine. Now, one particular car instance has a type of "Coupe" and an engine cylinder count of 6, while another might have the type of "SUV" with the engine cylinder count of 6. I'm trying set up the local expression mechanism so that I could filter the cars by saying something like: I want all the cars of type "SUV" with engines with at least 6 cylinders to do something (whatever something may be). The trick (obviously) is that I'm trying to develop the expression mechanism not just to work for cars and engines, but any generic QObject. So, for instance, looking at my earlier posts, this could be represented as: ((Type == "SUV") AND (Engine.CylinderCount >= 6)) if the QObject that was provided was the type Car (hence, Car.Type(), and Car.Engine().CylinderCount()). The idea is to use this is in a plugin library where we want to allow the user freedom to develop logical expressions on their own custom datatypes that inherit from QObject (hence the requirement for reflection).

wysota
15th November 2010, 18:43
To be honest I would probably use some scripting language (like QtScript) and I would simply expose appropriate properties to the script. I don't really see the benefit of using QObject here anywhere. I think in this situation it is more a burden than a benefit. I don't know if QtScript can directly operate on properties that are QObjects (which is very likely) but even if not, it shouldn't be too hard to add appropriate pieces of code. And you wouldn't be limited to QObjects too. And you would get all the power of JavaScript for free.

Sanuden
15th November 2010, 18:47
Interesting, I hadn't considered that route at all. I've looked into QtScript a bit for some other things I am working on, and now that you mention it, this might be a good fit for this as well. Thanks for the suggestions, I'll play around and see what I can come up with.

wysota
15th November 2010, 19:01
It seems it works just fine:

#include <QtScript>
#include <QtCore>

int main(int argc, char **argv){
QCoreApplication app(argc, argv);
QScriptEngine eng;
QObject o1;
QObject o2;
o1.setProperty("child", qVariantFromValue(&o2));
o2.setProperty("val", 7);
QScriptValue o1Obj = eng.newQObject(&o1);
eng.globalObject().setProperty("obj", o1Obj);
qDebug() << eng.evaluate("obj.child.val == 7").toBool();
qDebug() << eng.evaluate("obj.child.val == 8").toBool();
return 0;
}

Program result:

true
false

To avoid the "obj" property just change the global property of the whole script to your main object (o1Obj). You might want to copy the original object's properties to have access to JS libraries.

Sanuden
15th November 2010, 19:06
Very nice, yeah, I think I can make this do what I want. Thanks for all your help.