Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly use qRegisterMetaType on a class derived from QObject?

Tags:

c++

reflection

qt

I've been searching far and wide for an answer to this but to no avail. My lament is as follows:

I have a ClassA that roughly looks like this:

class ClassA : public QObject {
    Q_OBJECT
public:
    ClassA() { mName = "lol"; }
    ~ClassA();
    void ShowName() { std::cout << mName << std::endl; }
    std::string mName;
};

Of course, since I use moc, this class is actually split into cpp and hpp in my project but that part is not the issue here.

Please note that I do not use Q_DECLARE_METATYPE on purpose because I don't actually need its features (QVariant expansion) right now. I only care about runtime instantiation.

The issue here is that Q_OBJECT forbids the copy and assignment constructors. Due to that, I have to apply qRegisterMetaType not to ClassA itself but to ClassA* which seems to work fine at first glance.

Now, I want to create this class dynamically at runtime from a string and run the method ShowName(). I'm doing that like this:

int main() {
    qRegisterMetaType<ClassA*>("ClassA*");

    int id = QMetaType::type("ClassA*");
    std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)

    ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
    myclass->ShowName(); // Segfaults, oh dear

    return 0;
}

Now, there is my issue. I don't seem to actually have a correctly constructed object there.

If we change the class to look like this:

class ClassA : public QObject {
    Q_OBJECT
public:
    ClassA() { mName = "lol"; }
    ClassA(const ClassA& other) { assert(false && "DONT EVER USE THIS"); }
    ~ClassA();
    void ShowName() { std::cout << mName << std::endl; }
    std::string mName;
};

then we can change our program accordingly to:

int main() {
    qRegisterMetaType<ClassA>("ClassA");

    int id = QMetaType::type("ClassA");
    std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)

    ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
    myclass->ShowName(); // "lol", yay

    return 0;
}

Obviously I could just use my fake overwritten copy constructor but it doesn't feel right and Qt suggests against that and instead suggests the use of pointers to QObjects only.

Does anyone see what's wrong here? Also, I am aware there are similar questions on SO but none of them tackle this exact problem.

like image 752
svenstaro Avatar asked Oct 24 '11 07:10

svenstaro


3 Answers

A few things:

  • The reason that registering ClassA* isn't working is because your call to construct() is constructing a pointer to a ClassA object, but not an actual object.

  • It is worthy of noting the following quote from the QMetaType documentation:

Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.

  • Take a look at Qt's implementation of qMetaTypeConstructHelper:

    template <typename T> void *qMetaTypeConstructHelper(const T *t) {     if (!t)         return new T();     return new T(*static_cast<const T*>(t)); } 

and note their usage of the copy constructor. This being the case, you have two ways around the problem:

1) Provide a copy constructor (which you have done)

2) Provide a specialization of qMetaTypeConstructHelper that doesn't use the copy constructor:

template <> void *qMetaTypeConstructHelper<ClassA>(const ClassA *) {     return new ClassA(); } 
like image 65
Chris Avatar answered Sep 30 '22 09:09

Chris


If you want to create instances of QObject classes by name, you can use QMetaObject instead of QMetaType.

First, you have to declare your constructor as invokable:

class ClassA : public QObject {
    Q_OBJECT
public:
    Q_INVOKABLE ClassA() { mName = "lol"; }
    ~ClassA();
    void showName() { std::cout << mName << std::endl; }
    std::string mName;
};

Then you have to create your own registration system for the classes you want to instantiate, and populate it manually:

int main(int argc, char *argv[])
{    
    // Register your QObject derived classes
    QList<const QMetaObject*> metaObjectList;
    metaObjectList << &ClassA::staticMetaObject;

    // Index the classes/metaobject by their names
    QMap<QString, const QMetaObject*> metaObjectLookup;
    foreach(const QMetaObject *mo, metaObjectList) {
        metaObjectLookup.insert(mo->className(), mo);
    }

And finally you'll be able instantiate by name any registered class:

    const QMetaObject * myMetaObject = metaObjectLookup.value("ClassA", 0);
    if(!myMetaObject)
    {
        // The class doesn't exist
        return 1;
    }

    ClassA *myObject =
            static_cast<ClassA*>(myMetaObject->newInstance());
    if(!myObject)
    {
        // Couldn't create an instance (constructor not declared Q_INVOKABLE ?)
        return 1;
    }
    myObject->showName();

    return 0;
}
like image 45
alexisdm Avatar answered Sep 30 '22 08:09

alexisdm


Here's an update to Chris' solution #2 for Qt 5:

namespace QtMetaTypePrivate {
    template <>
    struct QMetaTypeFunctionHelper<ClassA, true> {
        static void Delete(void *t)
        {
            delete static_cast<ClassA*>(t);
        }

        static void *Create(const void *t)
        {
            Q_UNUSED(t)
            return new ClassA();
        }

        static void Destruct(void *t)
        {
            Q_UNUSED(t) // Silence MSVC that warns for POD types.
            static_cast<ClassA*>(t)->~ClassA();
        }

        static void *Construct(void *where, const void *t)
        {
            Q_UNUSED(t)
            return new (where) ClassA;
        }
    #ifndef QT_NO_DATASTREAM
        static void Save(QDataStream &stream, const void *t)
        {
            stream << *static_cast<const ClassA*>(t);
        }

        static void Load(QDataStream &stream, void *t)
        {
            stream >> *static_cast<ClassA*>(t);
        }
    #endif // QT_NO_DATASTREAM
    };
}

If your ClassA doesn't implement operator<< and operator>> helpers for QDataStream, comment out the bodies of Save and Load or you'll still have a compiler error.

like image 21
Mark Visser Avatar answered Sep 30 '22 07:09

Mark Visser