Order of qRegisterMetaType calls affects program behaviour
-
I'm doing some R&D work on properties and ran into some strange behaviour:
I've declared two classes derived from QObject:
class NestedProperty: public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(int count READ count WRITE setCount) ... } Q_DECLARE_METATYPE(NestedProperty*)
and
typedef QList<int> IntList; Q_DECLARE_METATYPE(IntList) class PropertyTest: public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(NestedProperty* test READ test WRITE setTest NOTIFY testChanged) Q_PROPERTY(IntList list READ list WRITE setList NOTIFY listChanged) ... }
In PropertyTest constructor i've called
qRegisterMetaType<IntList>("IntList"); qRegisterMetaType<NestedProperty*>("NestedPropertyPtr");
Somewhere in the code i iterate over all the PropertyTest properties searching for UserType properties:
QMetaProperty meta_prop; for (int i=0; i<prop_count; ++i) { meta_prop = in_meta_obj->property(i); qDebug() << "meta_prop:" << meta_prop.type() << meta_prop.userType() << meta_prop.typeName(); if (meta_prop.type() >= QVariant::UserType && in_recursive == true) { const QMetaObject* sub_meta_object = QMetaType::metaObjectForType(meta_prop.type()); if (sub_meta_object == 0) continue; qDebug() << "sub_meta_object:" << sub_meta_object->className(); QVariant sub_obj_var = meta_prop.read(in_obj); if (sub_obj_var.isValid() == false) continue; qDebug() << "sub_obj_var type:" << sub_obj_var.type(); QObject* sub_obj = qvariant_cast<QObject *>(sub_obj_var); if (sub_obj == nullptr) continue; ... } }
The call
const QMetaObject* sub_meta_object = QMetaType::metaObjectForType(meta_prop.type());
returns nullptr for propertiestest
andlist
The weird thing is that when i change the order of qRegisterMetaType to
qRegisterMetaType<NestedProperty*>("NestedPropertyPtr"); qRegisterMetaType<IntList>("IntList");
everything works fine and i'm able to retrieve meta object ptr for
test
andlist
Maybe i should mention that both classes are declared in the same header file (- oder is the same as above).
Any idea why this is happening and how i can avoid such things in the future?
BR -
@ttuna
Hello,Maybe i should mention that both classes are declared in the same header file (- oder is the same as above).
Create separate headers for them. I'm not completely sure for the current release, but at least to my knowledge the
moc
will not be able to parse up the macros for both your classes when they're in the same header (the source file can be the same, though). Someone please correct me if I'm wrong about this.Kind regards.
-
@ttuna
Hello,Maybe i should mention that both classes are declared in the same header file (- oder is the same as above).
Create separate headers for them. I'm not completely sure for the current release, but at least to my knowledge the
moc
will not be able to parse up the macros for both your classes when they're in the same header (the source file can be the same, though). Someone please correct me if I'm wrong about this.Kind regards.
@kshegunov
But why would the behaviour change by switching the lines in the cpp file? -
@kshegunov
But why would the behaviour change by switching the lines in the cpp file?@ttuna
Hello,
If I had to guess, because thisQ_DECLARE_METATYPE(TestPropertyObject*)
apears before the other declaration. As I said, I'm not 100% sure about it, but I think it's quite trivial to split up the header and test my theory?Kind regards.
-
@ttuna
Hello,
If I had to guess, because thisQ_DECLARE_METATYPE(TestPropertyObject*)
apears before the other declaration. As I said, I'm not 100% sure about it, but I think it's quite trivial to split up the header and test my theory?Kind regards.
@kshegunov
I have separated the class declarations in two different files but the behaviour is still the same. -
@devel
I've edited my code in the post for better understanding and messed it up - sorry.
TestPropertyObject = NestedProperty so far ... fixed it in the OP. -
@ttuna
Strange, I haven't faced such a problem before. I just don't know what to suggest. :|@kshegunov
Thanks anyway.Maybe someone has time to spare trying to reproduce this effect.
BR -
I've added some debug messages (- adapted the OP) and got some strange output:
When i first register
IntList
andNestedProperty*
afterwards (-which is the non-working case) the debug output is:meta_prop: QVariant::IntList 1026 NestedProperty* meta_prop: QVariant::IntList 1024 IntList
After switching the register calls the debug output looks like this:
meta_prop: QVariant::NestedProperty* 1024 NestedProperty* meta_prop: QVariant::NestedProperty* 1026 IntList
The weird thing is that the
meta_prop.type()
call always returns the type which is registered first. But why?!? -
Hi,
Can you share somewhere a small compilable sample project that shows this behavior ?
On a side note, you can ask questions also on the interest mailing list You'll find there Qt's developers/maintainers (this forum is more user oriented)
-
Hi,
Can you share somewhere a small compilable sample project that shows this behavior ?
On a side note, you can ask questions also on the interest mailing list You'll find there Qt's developers/maintainers (this forum is more user oriented)
-
Hi,
Can you share somewhere a small compilable sample project that shows this behavior ?
On a side note, you can ask questions also on the interest mailing list You'll find there Qt's developers/maintainers (this forum is more user oriented)
-
@ttuna
Hello,
It took the better half of an hour, but I found what your problem was. Before giving you the solution though I want to point out some things, please don't take offence even if they sound forceful.- Don't use template needlessly!!! Why this:
template<typename T> static QMetaProperty GetProperty(T& in_obj, QString in_name)
is this not this:
static QMetaProperty GetProperty(QObject & in_obj, QString in_name)
There's no
staticMetaObject
without theQ_OBJECT
macro.-
Don't, I repeat, do not derive from
QApplication
/QCoreApplication
without a very, very good reason, unless you want to enter a world of hurt. It's a very bad idea, especially since you're not doing anything special. -
When registering meta types with the runtime give your own names only to the ones you've typedef-ed. E.g.:
qRegisterMetaType<IntList>("IntList"); //< This is fine qRegisterMetaType<TestPropertyObject*>("TestPropertyObject*"); //< Don't do that!
Instead use:
qRegisterMetaType<IntList>("IntList"); qRegisterMetaType<TestPropertyObject*>();
-
I suggest putting these types of constructs -
Q_DECLARE_FLAGS(JsonFileFlags, JFFlags)
- in the global scope, not in the class. -
What's wrong with naming your enums directly?
typedef enum { PROPERTY_ACCESSOR_OPERATOR_UNKNOWN = 0, // ... } CheckOperator;
Are you coming from C? I don't see a reason to have an unnamed type and then typedef it.
-
If you want to serialize your objects, consider writing operators for
QTextStream
/QDataStream
instead of having functions like:PropertyPersistor::toFile(const QObject &, const QString &, const bool)
. Even if you use such functions, make them non-static, what's the point of giving the object as a parameter, you gain nothing. -
You have too many inline functions for my taste. If you want to have them inlined consider doing it by splitting the definition from the declaration and marking them as such explicitly, i.e.:
// All this goes in the header class MyClass { void myInlineFunction(); }; inline void MyClass::myInlineFunction() { // ... Code }
It makes for much easier reading and the class' interface is clearly visible.
- Just a remark - your project file is very confusing, my QtCreator seems to be unable to deduce that changes have been made, so I had to rebuild all times and times again. Perhaps it's like this because you're using MS Visual Studio, I don't know, but it looks strange.
The solution
bool RegisterTestAppl::init() { // ... id = qRegisterMetaType<IntList>("IntList"); id = qRegisterMetaType<TestPropertyObject *>(); // ... } static QVariantMap GetPropertyVariantMap(const QObject* in_obj, const QMetaObject* in_meta_obj, const bool in_recursive) { // ... if (meta_prop.type() >= QVariant::UserType && in_recursive == true) { int meta_prop_type = meta_prop.userType(); //< !!! QMetaProperty::userType() !!! const QMetaObject* sub_meta_object = QMetaType::metaObjectForType(meta_prop_type); // ... } }
Kind regards.
-
@ttuna
Hello,
It took the better half of an hour, but I found what your problem was. Before giving you the solution though I want to point out some things, please don't take offence even if they sound forceful.- Don't use template needlessly!!! Why this:
template<typename T> static QMetaProperty GetProperty(T& in_obj, QString in_name)
is this not this:
static QMetaProperty GetProperty(QObject & in_obj, QString in_name)
There's no
staticMetaObject
without theQ_OBJECT
macro.-
Don't, I repeat, do not derive from
QApplication
/QCoreApplication
without a very, very good reason, unless you want to enter a world of hurt. It's a very bad idea, especially since you're not doing anything special. -
When registering meta types with the runtime give your own names only to the ones you've typedef-ed. E.g.:
qRegisterMetaType<IntList>("IntList"); //< This is fine qRegisterMetaType<TestPropertyObject*>("TestPropertyObject*"); //< Don't do that!
Instead use:
qRegisterMetaType<IntList>("IntList"); qRegisterMetaType<TestPropertyObject*>();
-
I suggest putting these types of constructs -
Q_DECLARE_FLAGS(JsonFileFlags, JFFlags)
- in the global scope, not in the class. -
What's wrong with naming your enums directly?
typedef enum { PROPERTY_ACCESSOR_OPERATOR_UNKNOWN = 0, // ... } CheckOperator;
Are you coming from C? I don't see a reason to have an unnamed type and then typedef it.
-
If you want to serialize your objects, consider writing operators for
QTextStream
/QDataStream
instead of having functions like:PropertyPersistor::toFile(const QObject &, const QString &, const bool)
. Even if you use such functions, make them non-static, what's the point of giving the object as a parameter, you gain nothing. -
You have too many inline functions for my taste. If you want to have them inlined consider doing it by splitting the definition from the declaration and marking them as such explicitly, i.e.:
// All this goes in the header class MyClass { void myInlineFunction(); }; inline void MyClass::myInlineFunction() { // ... Code }
It makes for much easier reading and the class' interface is clearly visible.
- Just a remark - your project file is very confusing, my QtCreator seems to be unable to deduce that changes have been made, so I had to rebuild all times and times again. Perhaps it's like this because you're using MS Visual Studio, I don't know, but it looks strange.
The solution
bool RegisterTestAppl::init() { // ... id = qRegisterMetaType<IntList>("IntList"); id = qRegisterMetaType<TestPropertyObject *>(); // ... } static QVariantMap GetPropertyVariantMap(const QObject* in_obj, const QMetaObject* in_meta_obj, const bool in_recursive) { // ... if (meta_prop.type() >= QVariant::UserType && in_recursive == true) { int meta_prop_type = meta_prop.userType(); //< !!! QMetaProperty::userType() !!! const QMetaObject* sub_meta_object = QMetaType::metaObjectForType(meta_prop_type); // ... } }
Kind regards.
@kshegunov Great posting. Could you please explain your point number 2? What's the problem with deriving from QApplication? Thanks in advance!
-
@kshegunov Great posting. Could you please explain your point number 2? What's the problem with deriving from QApplication? Thanks in advance!
@Wieland
Thanks. Well, I certainly overplayed this particular point, but the statement I believe is valid in principle. :)
QApplication
is the rootQObject
of the program and its full initialization is required before anything can practically be done with anything else. It manages an insane amount of static variables and sets a static global pointer of itself when it initializes. I'm warning against it, because some time ago I had terrible time debugging a library that extended it and because of some subtle differences between the window and linux loaders it had trouble with a global variable. Long story short, I ended up with two instances of a global variable one in the application address space and one in the library address space (at the time I thought that impossible, but well, that's life). Sufficed to say that particular library was not the best of codes, but some caution is advised. Additionally, there's not much gain or need to extend the application, unless you're doing some specific low-level stuff. A hint thatQApplication
is not supposed to be inherited is the missing private object constructor, i.e.:QApplication::QApplication(QApplicationPrivate &)
is nowhere to be found.Kind regards.
-
@ttuna
Hello,
It took the better half of an hour, but I found what your problem was. Before giving you the solution though I want to point out some things, please don't take offence even if they sound forceful.- Don't use template needlessly!!! Why this:
template<typename T> static QMetaProperty GetProperty(T& in_obj, QString in_name)
is this not this:
static QMetaProperty GetProperty(QObject & in_obj, QString in_name)
There's no
staticMetaObject
without theQ_OBJECT
macro.-
Don't, I repeat, do not derive from
QApplication
/QCoreApplication
without a very, very good reason, unless you want to enter a world of hurt. It's a very bad idea, especially since you're not doing anything special. -
When registering meta types with the runtime give your own names only to the ones you've typedef-ed. E.g.:
qRegisterMetaType<IntList>("IntList"); //< This is fine qRegisterMetaType<TestPropertyObject*>("TestPropertyObject*"); //< Don't do that!
Instead use:
qRegisterMetaType<IntList>("IntList"); qRegisterMetaType<TestPropertyObject*>();
-
I suggest putting these types of constructs -
Q_DECLARE_FLAGS(JsonFileFlags, JFFlags)
- in the global scope, not in the class. -
What's wrong with naming your enums directly?
typedef enum { PROPERTY_ACCESSOR_OPERATOR_UNKNOWN = 0, // ... } CheckOperator;
Are you coming from C? I don't see a reason to have an unnamed type and then typedef it.
-
If you want to serialize your objects, consider writing operators for
QTextStream
/QDataStream
instead of having functions like:PropertyPersistor::toFile(const QObject &, const QString &, const bool)
. Even if you use such functions, make them non-static, what's the point of giving the object as a parameter, you gain nothing. -
You have too many inline functions for my taste. If you want to have them inlined consider doing it by splitting the definition from the declaration and marking them as such explicitly, i.e.:
// All this goes in the header class MyClass { void myInlineFunction(); }; inline void MyClass::myInlineFunction() { // ... Code }
It makes for much easier reading and the class' interface is clearly visible.
- Just a remark - your project file is very confusing, my QtCreator seems to be unable to deduce that changes have been made, so I had to rebuild all times and times again. Perhaps it's like this because you're using MS Visual Studio, I don't know, but it looks strange.
The solution
bool RegisterTestAppl::init() { // ... id = qRegisterMetaType<IntList>("IntList"); id = qRegisterMetaType<TestPropertyObject *>(); // ... } static QVariantMap GetPropertyVariantMap(const QObject* in_obj, const QMetaObject* in_meta_obj, const bool in_recursive) { // ... if (meta_prop.type() >= QVariant::UserType && in_recursive == true) { int meta_prop_type = meta_prop.userType(); //< !!! QMetaProperty::userType() !!! const QMetaObject* sub_meta_object = QMetaType::metaObjectForType(meta_prop_type); // ... } }
Kind regards.
@kshegunov
First of all, i would like to thank you for your extensive work.I should have mentioned that the code example is boiled down from a project with many programmers working on. If there are discrepancies like weird function templates etc. this is mostly the effect of cut down code fragments.
Concerning your objection against
QApplication
derivation you might be right. It's dangerous but i think it works great if you do it the right way (- though extending QApplication in a library is quite strange ;-)Please could you explain your argumentation for 3.)?
I haven't found any explanations why i shouldn't use a name for TestPropertyObject* type registration.BR
-
@kshegunov
First of all, i would like to thank you for your extensive work.I should have mentioned that the code example is boiled down from a project with many programmers working on. If there are discrepancies like weird function templates etc. this is mostly the effect of cut down code fragments.
Concerning your objection against
QApplication
derivation you might be right. It's dangerous but i think it works great if you do it the right way (- though extending QApplication in a library is quite strange ;-)Please could you explain your argumentation for 3.)?
I haven't found any explanations why i shouldn't use a name for TestPropertyObject* type registration.BR
@ttuna
Hello,
I'm glad I could help.I should have mentioned that the code example is boiled down from a project with many programmers working on.
Yes, I gathered it's a group effort, however a style guide and "do-or-don't" document might be a good things to consider, especially for large projects where many people contribute.
If there are discrepancies like weird function templates etc. this is mostly the effect of cut down code fragments.
Well, the function templates are just not needed. How it's now, for each type that you pass to the function a new one will be generated in the binary, that is every
QObject
subclass will have its own function. No need for that, since all objects that have dynamic properties already extend fromQObject
, right?(- though extending QApplication in a library is quite strange ;-)
To be honest, putting it in a library would be the singular reason I'd consider deriving from it. Otherwise just using
main()
does seem so much simpler and shorter (no extra class that is).I haven't found any explanations why i shouldn't use a name for TestPropertyObject* type registration.
Actually this directly comes from the documentation of qRegisterMetaType. See the warning above the note near the end of the function description.
Kind regards.