Meta-type id of an object


  • Qt Champions 2016

    Hello,
    For a project I'm working on I have to marshal some objects through a network stream. The most obvious approach seemed to serialize/deserialize them based on their meta-type id's at the entry/exit point of my application. So to do that I'd wish to extract the meta-type id for each of them. Note that my objects are not descendants of QObject, but I've made them gadgets with Q_GADGET. While QObject provides QObject::metaObject there's no such straight-forward way to retrieve the meta-object for gadgets. I suppose I could get it from staticMetaObject, and even if I did that is there a direct way to retrieve the meta-type id?

    The only way I could find was to retrieve the class name from the meta-object and then use QMetaType::type with it to find out what the id is corresponding to the class. Is there a better/more convenient method for doing this? Am I overcomplicating my problem?

    As a side question, can I invoke signals based on the type id I'm getting (if they are supposedly already declared in the C++ code)?
    Edit: I'll post this as a separate thread when I face its implementation.

    Kind regards.


  • Lifetime Qt Champion

    Hi,

    Isn't that what QMetaType provides ? With qMetaTypeId to get the id of your type ?


  • Qt Champions 2016

    @SGaist
    Hello Samuel,
    While I appreciate you taking the time to answer, I don't think it's helping my case. Yes, QMetaType is what provides the needed API, however I was expecting more of an advice than a reference to the docs. :)
    Still, I may not have explained my dilemma well, so I'm going to expand a bit (directly using the classes' names from my lib):

    Suppose in a library I have a class that's called QMpiMessage, which is to be the base for the MPI messages that I'm going to marshal through the MPI infrastructure (a low-latency high-throughput network). The user (programmer) is to derive from that base and define his/hers own messages that could be used to communicate between the nodes. Now, I've made the base to be a gadget to put the meta-type information in that class (Noting here that only declaring it as meta-type with Q_DECLARE_METATYPE should work as well, and most probably is the better way, as these objects of type QMpiMessage (and descendants) should hold the message data, so I probably will want switch to it at some point).

    So the use case for the send operation is as follows:

    1. The user creates a message object (derived from QMpiMessage).
    2. He/she passes it to the QMpiNode (QObject subclass that manages communication with a computational node) for sending. (I have void QMpiNode::send(const QMpiMessage *, bool synchronous = false) for that purpose)
    3. The message object is serialized in a QByteArray
    4. Depending on whether the message is synchronous its generated event is either processed immediately and synchronously, or it's is scheduled through Qt's event system to be processed at a later time.

    Right, so to have the serialization performed transparently I'm about to be using the meta-type system. However, since the type of the object referenced by the base class pointer is not known at compile-time, I have to retrieve the meta-type id from the QMpiMessage * - the base class' pointer. QObject provides the virtual const QMetaObject * QObject::metaObject() const exactly for such reasons, at least I assume that's the reason, but my base class is not a QObject. The question boils down to:
    How can I retrieve the meta-type id at runtime, provided I don't inherit from QObject?

    Notice that if it's better/easier I could convert the base class to extend QObject if needed, but I'd wish to avoid it if possible. Since this is a "framework" in the making, I'm open to any other solution(s) that suit the end result I'm after.

    With qMetaTypeId to get the id of your type?

    This ain't doing me any good, since I need to retrieve the meta-type ids at runtime (not at compile time) for an object belonging to an hierarchy (the base class is known). The derived class can't be known at compile time, because the base is from the library I'm working on and the subclasses are user-defined.

    Kind regards,
    Konstantin.



  • Hi! Your function void QMpiNode::send(const QMpiMessage * message, bool synchronous) seems to be the place where you want to serialize message into payload, right? Why can't message do it by itself? like QByteArray QMpiMessage::serialize() const?


  • Moderators

    Q_DECLARE_METATYPE only registers a type in meta type system. It's not enough to get reflection features out of a type this way (it does not create a QMetaObject), so for the thing to be useful at all you need at least Q_GADGET in both base and derived classes.

    Assuming base and derived are Q_GADGETs you want to get a static member (staticMetaObject) of a derived class having only a pointer to the base class. The only way to go "up" in the inheritance chain in c++, when you don't know types at compile time, is via virtualization, and since Q_GADGET does not provide any, you need to provide it yourself (pretty much one of the things Q_OBJECT does).

    For example:

    #define FOO Q_GADGET \
    public : \
    virtual const QMetaObject* metaObject() { return &staticMetaObject; }
    
    class Base
    {
        FOO
    };
    
    class Derived : public Base
    {
        FOO
    };
    

    With this you can introspect both derived and base like this:

    Base* ptr = new Derived();
    qDebug() << ptr->metaObject()->className(); //prints "Derived"
    qDebug() << ptr->metaObject()->superClass()->className(); //prints "Base"
    

  • Qt Champions 2016

    Hello,
    I most certainly was not expecting such interest, based on my other questions, and so much good advice. I want to thank you in advance, before going in to respond in turn to each of you.

    @Wieland

    Your function void QMpiNode::send(const QMpiMessage * message, bool synchronous) seems to be the place where you want to serialize message into payload, right?

    This is exactly correct. I'm planing to serialize the message inside the send function.

    Why can't message do it by itself? like QByteArray QMpiMessage::serialize() const?

    It will, and it should, since as far as I can see there's no other way to delegate the responsibility through the inherited classes, and naturally the base class couldn't possibly know how to serialize the derived classes' data. I actually think of implementing a "virtual stream operator" of sorts, something like this:

    QDataStream & operator << (QDataStream & stream, QMpiMessage & message)
    {
        stream << messageTypeId; //< This is why I want to retrieve the meta-type id for the QMpiMessage derived classes.
        message.serialize(stream);
    }
    

    where one'd have virtual void QMpiMessage::serialize(QDataStream &) const = 0 in the base class.

    @Chris-Kawa

    Q_DECLARE_METATYPE only registers a type in meta type system. It's not enough to get reflection features out of a type this way (it does not create a QMetaObject), so for the thing to be useful at all you need at least Q_GADGET in both base and derived classes.

    You're right of course, it seems I wasn't much thinking when I wrote the post ... :|

    Assuming base and derived are Q_GADGETs you want to get a static member (staticMetaObject) of a derived class having only a pointer to the base class. The only way to go "up" in the inheritance chain in c++, when you don't know types at compile time, is via virtualization, and since Q_GADGET does not provide any, you need to provide it yourself (pretty much one of the things Q_OBJECT does).

    Right, so what about making my own macro instead that shouldn't even depend on Q_GADGET that'd do the virtual's overriding? Something like this:

    #define MPI_MESSAGE(ClassName) \
        virtual int metaTypeId() const {  return qMetaTypeId<ClassName>(); }
    
    class MyMpiMessage : public QMpiMessage
    {
        MPI_MESSAGE(MyMpiMessage)
    }
    

    With the understanding that all of QMpiMessage descendants must be declared with the meta-type system? This would also allow me to not worry about the base class being passed as a pointer. Additionally, It will permit to change the send() prototype to:

    void QMpiNode::send(const QMpiMessage &, bool synchronous = false);
    

    and leave the user to worry about memory management of the messages?

    Thanks again guys for the input!
    Kind regards.



  • @kshegunov said:

    #define MPI_MESSAGE(ClassName)
    virtual int metaTypeId() const { return qMetaTypeId<ClassName>(); }

    This won't work because you can't use qMetaTypeId<>() before Q_DECLARE_METATYPE<>. Thus you need to use the runtime check QMetaType::type(). The following should work:

    #define xstr(s) stringify(s)
    #define stringify(s) #s
    #define MPI_MESSAGE(ClassName) \
        virtual int metaTypeId() const { return QMetaType::type( stringify(ClassName) ); }
    

    Note that you also have to call qRegisterMetaType<MyMpiMessage>(); before you use the function the first time (you'd usually do it in main.cpp).


  • Qt Champions 2016

    @Wieland
    I don't understand. The macro will be expanded before compilation and will generate the virtual override for the method. Said override will not be called until the program is run and the meta-type id is actually needed (in the QMpiNode::send() function). So I don't see a reason why a function call would be made before the Q_DECLARE_METATYPE macro expansion.

    Note that you also have to call qRegisterMetaType<MyMpiMessage>(); before you use the function the first time (you'd usually do it in main.cpp).

    I believe qMetaTypeId<MyMpiMessage>() automatically registers the template argument with the runtime, but I might be wrong. I'll have to check.

    Kind regards.



  • qMetaTypeId will break the compilation if used before Q_DECLARE_METATYPE. I tested it before I posted my reply to avoid talking bullsh.. :-)

    qmpimessage.h

    #ifndef QMPIMESSAGE_H
    #define QMPIMESSAGE_H
    
    #include <QString>
    
    #define xstr(s) stringify(s)
    #define stringify(s) #s
    #define MPI_MESSAGE(ClassName) \
        virtual int metaTypeId() const { return QMetaType::type( stringify(ClassName) ); }
    
    class QMpiMessage
    {
    public:
        QMpiMessage();
    
        virtual QString sayHi() const;
    };
    
    #endif // QMPIMESSAGE_H
    

    qpimessage.cpp

    #include "qmpimessage.h"
    
    QMpiMessage::QMpiMessage()
    {
    }
    
    QString QMpiMessage::sayHi() const
    {
        return "base";
    }
    

    mympimessage.h

    #ifndef MYMPIMESSAGE_H
    #define MYMPIMESSAGE_H
    
    #include <QMetaType>
    #include "qmpimessage.h"
    
    class MyMpiMessage : public QMpiMessage
    {
    public:
        MPI_MESSAGE(MyMpiMessage)
        MyMpiMessage();
        QString sayHi() const;
    };
    
    Q_DECLARE_METATYPE(MyMpiMessage)
    
    #endif // MYMPIMESSAGE_H
    

    mympimessage.cpp

    #include "mympimessage.h"
    
    MyMpiMessage::MyMpiMessage()
        : QMpiMessage()
    {
    }
    
    QString MyMpiMessage::sayHi() const
    {
        return "derived";
    }
    

    main.cpp

    #include <QApplication>
    #include <QDebug>
    #include "mympimessage.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        qRegisterMetaType<MyMpiMessage>();
    
        MyMpiMessage mmm;
        qDebug() << mmm.sayHi();
        qDebug() << mmm.metaTypeId();
        qDebug() << qMetaTypeId<MyMpiMessage>();
    
        return 0;
    }
    

  • Qt Champions 2016

    @Wieland
    Damn! You're obviously right and I think the Q_DECL_CONSTEXPR is the reason for this somewhat curious behavior:

    template <typename T>
    inline Q_DECL_CONSTEXPR int qMetaTypeId();
    

    Although, I think your macro could be simplified a bit (unless I'm completely clueless again):

    #define MPI_MESSAGE(ClassName) \
        virtual int metaTypeId() const { return QMetaType::type("##ClassName##"); }
    

    Note that you also have to call qRegisterMetaType<MyMpiMessage>();

    This also makes much more sense to me now ...



  • @kshegunov said:

    Although, I think your macro could be simplified a bit

    This might be true. To be honest, I have a cheat book for preprocessor magic because I find it too hard to make things right without. ;-)


  • Qt Champions 2016

    @Wieland said:

    This might be true. To be honest, I have a cheat book for preprocessor magic because I find it too hard to make things right without.

    One of the reasons I hate macros in the first place, they expand and reexpand themselves endlessly it seems ... :D


  • Qt Champions 2016

    Well, just letting you know that Chris' suggestion seems the most painless way to do the thing I need, so I'm going with it.
    Thanks all!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.