7.12.10

The Beauty of Qt 3: Implicit Sharing

To maximize resource usage and minimize copying, Qt uses implicit data sharing in many classes, so that the data is copied only when a function writes to it. This trick is also referred to as flyweight pattern sometimes.

Now let's take QByteArray as an example to see how it's implemented. It uses a private struct of Data to track the shared data:
struct Data {
  QBasicAtomicInt ref; // reference count, the operation on it is atomic
  int alloc; // allocated space for the data
  int size; // actual size of the data, not counting the ending '\0' added by QByteArray
  char *data; // point to the data
  char array[1]; // where the data is stored, and the data always end with '\0'
};


Here, we use both the pointer of data and array just because the data might actually be stored in another object. When an object is copied, e.g. through the assignment operator, it only copies the pointer to the shared data:
QByteArray &QByteArray::operator=(const QByteArray & other)
{
  // increase the reference count of the shared data it's supposed to be used
  other.d->ref.ref();

  // decrease the reference count of the share data currently used, and free it if no one else is using
  if (!d->ref.deref())
    qFree(d);

  // point to the shared data
  d = other.d;

  return *this;
}


On the other hand, if it's to be changed by e.g. the resize() function, it copies the data if any other object also shares it:
void QByteArray::resize(int size)
{
  if (size <= 0) {
    // if the target is no bigger than 0, points to an empty data block
    Data *x = &shared_empty;
    x->ref.ref();
    if (!d->ref.deref())
      qFree(d);
    d = x;
  } else if (d == &shared_null) {
    // if currently a null array, just create a new data block
    Data *x = static_cast<data *>(qMalloc(sizeof(Data)+size));
    Q_CHECK_PTR(x);
    x->ref = 1;
    x->alloc = x->size = size;
    x->data = x->array;
    x->array[size] = '\0';
    (void) d->ref.deref();
    d = x;
  } else {
    // if any other object uses this data block, or the current memory is too small or too big
    // reallocate space, and copy the data for it
    // note that this operation might consume some time if the data is huge
    if (d->ref != 1 || size > d->alloc || (size < d->size && size < d->alloc >> 1))
      realloc(qAllocMore(size, sizeof(Data)));
    if (d->alloc >= size) {
      d->size = size;
      if (d->data == d->array) {
        d->array[size] = '\0';
      }
    }
  }
}


With this in mind, let's see how to use QSharedData and QSharedDataPointer to implement our own implicit shared data objects.
// first implement your data object inheriting from QSharedData, which provides the reference count
class SharedData: public QSharedData
{
public:
  SharedData()
    : QSharedData()
    , var(0)
  {}

  SharedData(const SharedData &other)
    : QSharedData(other)
    , var(other.var)
  {}

  int var;
};

// then the data owner
class DataOwner
{
public:
  DataOwner()
  : d(new SharedData)
  {}

  DataOwner(int var)
  : d(new SharedData)
  {
    // for write access, the -> operator will automatically copy the shared data if needed
    d->var = var;
  }

private:
  // this template class hides all the details for implicit sharing
  // therefore, no need to provide copy constructor or assignment operator
  QSharedDataPointer<SharedData> d;
};


Quite simple, right ;) Then just proceed to implement explicit shared data objects using QExplicitlySharedDataPointer yourself.

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;
}