1.12.10

The Beauty of Qt 2: Meta Object

Besides the D-pointers, another interesting thing in Qt is the Q_OBJECT macro. It provides the access to meta objects, which enables you to enjoy more features of a QObject, e.g. signals and slots. The meta object provides information about class name, properties and methods of a QObject, which is also known as reflection.

Using QMetaObject, you can print the information of the class name, etc.:
QObject obj;
const QMetaObject *metaObj = obj.metaObject();
qDebug() << "class name: " << metaObj->className();
qDebug() << "class info count: " << metaObj->classInfoCount();
qDebug() << "methods: ";
// starting from QMetaObject::methodOffset() so it won't display the methods inherited
for (int i = metaObj->methodOffset(); i < metaObj->methodCount(); ++i)
  qDebug() << metaObj->method(i).methodType() << " " << metaObj->method(i).signature();


As none of these information is supported in pure C++, Qt uses the Meta Object Compiler (moc) to do all the tricks. It reads each header file, and generates a C++ source file (moc_*.cpp) containing the code for meta-object, if it finds a Q_OBJECT macro used in any class declaration. With the code generation approach, Qt not only gains the flexibility as in e.g. Java, but also keeps the performance and scalability as in C++.

Suppose we have the following simple class:
class MyObject : public QObject
{
  Q_OBJECT
public:
  explicit MyObject(QObject *parent = 0);

  void myFunc();

public slots:
  void mySlot(int myParam);

signals:
  void mySignal(int myParam);
};


The following code / information will be generated by moc, and can be retrieved through pointers defined in the anonymous struct of QMetaObject::d:
// pointed by QMetaObject::d.data, the beginning stored "as" a QMetaObjectPrivate struct
static const uint qt_meta_data_MyObject[] = {
  5, // revision, the internals have been changed several times
  0, // classname, offset of qt_meta_stringdata_MyObject

  // the following are defined as (number, index) pair
  0, 0, // classinfo
  2, 14, // we have two methods, starting at index 14 (i.e. the signal)
  0, 0, // properties
  0, 0, // enums/sets
  0, 0, // constructors

  0, // flags
  1, // signal counts

  // for signals slots, and properties, the signatures and parameters are offset of qt_meta_stringdata_MyObject
  // signals: signature, parameters, type, tag, flags
  18, 10, 9, 9, 0x05,

  // slots: signature, parameters, type, tag, flags
  32, 10, 9, 9, 0x0a,

  0 // eod
};

// pointed by QMetaObject::d.stringdata
static const char qt_meta_stringdata_MyObject[] = {
  "MyObject\0\0myParam\0mySignal(int)\0"
  "mySlot(int)\0"
};


The above information, as well as the information for its base class, is stored as the static meta object for this class:
const QMetaObject MyObject::staticMetaObject = {
  { &QObject::staticMetaObject, // pointer to its base class, stored at QMetaObject::d.superdata
    qt_meta_stringdata_MyObject, qt_meta_data_MyObject, 0 }
};


In this way, if you want to do type cast for QObject, instead of using the somewhat expensive operator of dynamic_cast, we can use qobject_cast. It exploits the benefits of the meta object system, thus avoiding the runtime type cast:
template <class T> inline T qobject_cast(QObject *object)
{
#if !defined(QT_NO_QOBJECT_CHECK)
  reinterpret_cast(0)->qt_check_for_QOBJECT_macro(*reinterpret_cast(object));
#endif
  return static_cast(reinterpret_cast(0)->staticMetaObject.cast(object));
}


Here, the trick is that the QMetaObject of the target type checks if the object inherits from it:
const QObject *QMetaObject::cast(const QObject *obj) const
{
  if (obj) {
    const QMetaObject *m = obj->metaObject();
    do {
      if (m == this)
        return obj;
    } while ((m = m->d.superdata));
  }
  return 0;
}


Also, moc will generate some code for each signal. Whenever a signal is emitted, this function will be called internally:
void MyObject::mySignal(int _t1)
{
  void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
  // it checks all the connected slots, and call each of them based on the connection type
  QMetaObject::activate(this, &staticMetaObject, 0, _a);
}


In the end, the slots are invoked by the generated qt_metacall function:
int MyObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
  // if this function is called by the super class, just return
  _id = QObject::qt_metacall(_c, _id, _a);
  if (_id < 0)
    return _id;

  // call the function based on its ID
  if (_c == QMetaObject::InvokeMetaMethod) {
    switch (_id) {
    case 0: mySignal((*reinterpret_cast< int(*)>(_a[1]))); break;
    case 1: mySlot((*reinterpret_cast< int(*)>(_a[1]))); break;
    default: ;
    }

    // remove the IDs "consumed" by this class so that in its subclass the ID always starts with 0, and the return value of -1 means already consumed
    _id -= 2;
  }

  return _id;
}


No comments:

Post a Comment