UNISA Chatter – Design patterns in C++ Part 8: Reflection using QT

See UNISA – Summary of 2010 Posts for a list of related UNISA posts. Continued from UNISA Chatter – Design patterns in C++ Part 6: Widgets … Validation and Regular Expressions using QT.

IMPORTANT POINT: It is important to emphasize that the intent of these posts are to share my learning's as I dig through the last three subjects of my part-time UNISA studies. The posts by no means promote concepts or technologies … they are pure information sharing for fellow students … although the highlight that we explore more technologies and concepts that we typically prefer :)

Today we explore QT’s implementation of the MetaObject Pattern, which provide information about properties and methods of a QObject.

Technology Description
QT MetaObject Compiler Also known as moc, generates the code that supports the reflection feature.
QMetaObject

Supports the following functions:

  • className()  returns the class name
  • superClass() returns a pointer to the QMetaObject of the base class or 0.
  • methodCount() returns the number of member functions of the class
  • … and lots more … refer to QT help :)
DestType* qobject_cast<DestType*>(*)

Using the typecast operator we can convert an expression from one type to another. The destination type (DestType) is derived from QObject, making the operator a downcast operator. See dynamic_cast in QT help for more information.

Properties versus Getters/Setters Getters and setters are faster and more efficient. QObject Properties, however, are more generic.
QVariant Union wrapper for all the basic types and allowed Q_PROPERTY types. The struct delivers a rich interface for conversions and validations.
DataObject

An extension to the QOject, which delivers additional features:

  • virtual interface for obtaining properties
  • copying and comparing functions
  • toString() function that returns a presentation of all the properties of the object in XML format

Here is an extract from the assignment code I wrote to explore the above:

Textbook Header File (take note of lines 10 – 15) …

    1: #ifndef _TEXTBOOK_H_
    2: #define _TEXTBOOK_H_
    3:  
    4: #include <QObject>
    5: #include <QString>
    6: #include <QMap>
    7:  
    8: class Textbook : public QObject {
    9:     Q_OBJECT
   10:     //{{Q_PROPERTY Marcos specified
   11:     Q_PROPERTY(QString author READ getAuthor  WRITE setAuthor);
   12:     Q_PROPERTY(QString title  READ getTitle   WRITE setTitle);
   13:     Q_PROPERTY(QString isbn   READ getIsbn    WRITE setIsbn);
   14:     Q_PROPERTY(uint    year   READ getYearPub WRITE setYearPub);
   15:     //}
   16:   public:
   17:     Textbook(QString title, QString author, QString isbn, uint year);
   18: //end
   19:     QString getAuthor() const;
   20:     QString getTitle() const;
   21:     QString getIsbn() const;
   22:     uint getYearPub() const;
   23:     QString toString() const;
   24: public slots:
   25:     void setTitle(const QString& newTitle);
   26:     void setIsbn(const QString &newIsbn);
   27:     void setYearPub(uint newYear);
   28:     void setAuthor(const QString& newAuthor);
   29:  
   30: //start
   31: private:
   32:     uint m_YearPub;
   33:     QString m_Title, m_Author, m_Isbn;
   34: };
   35: #endif
   36: Test Program
   37: #include "textbook.h"
   38: #include <QDebug>
   39: #include <QMetaObject>
   40: #include <QMetaProperty>
   41: #include <QStringList>
   42:  
   43: QString objToString(const QObject* obj) {
   44:     QStringList result;
   45:     const QMetaObject* meta = obj->metaObject();
   46:     result += "\n";
   47:     result += QString("Properties: %1").arg(meta->propertyCount());
   48:     result += QString("Property Macro methods: %1").arg(meta->methodCount());
   49:     result += QString("class %1 : public %2 {").arg(meta->className())
   50:         .arg(meta->superClass()->className());
   51:     for (int i=0; i < meta->propertyCount(); ++i) {
   52:         const QMetaProperty qmp = meta->property(i);
   53:         QVariant value = obj->property(qmp.name());
   54:         result += QString("  %1 %2 = %3;")
   55:             .arg(qmp.type())
   56:             .arg(qmp.name())
   57:             .arg(value.toString());
   58:     }
   59:     result += "};";
   60:     return result.join("\n");
   61: }
   62:  
   63: //end
   64: //start id=client
   65: int main() {
   66:     Textbook* t1 = new Textbook("The C++ Programming Language","Stroustrup", "0201700735", 1997);
   67:     Textbook* t2 = new Textbook("XML in a Nutshell", "Harold","0596002920", 2002);
   68:     Textbook* t3 = new Textbook("UML Distilled", "Fowler", "0321193687", 2004);
   69:     Textbook* t4 = new Textbook("Design Patterns", "Gamma", "0201633612",1995);
   70:     { /* Inner block for demonstration purposes */
   71:       qDebug() << objToString(t1);
   72:       qDebug() << objToString(t2);
   73:       qDebug() << objToString(t3);
   74:       qDebug() << objToString(t4);
   75:     } /* End of block - local variables destroyed. */
   76:     return 0;
   77: }
   78: //end

This results in the following code being generated and added to the resultant program …

    1: /****************************************************************************
    2: ** Meta object code from reading C++ file 'textbook.h'
    3: **
    4: ** Created: Wed Jun 2 07:32:12 2010
    5: **      by: The Qt Meta Object Compiler version 61 (Qt 4.5.2)
    6: **
    7: ** WARNING! All changes made in this file will be lost!
    8: *****************************************************************************/
    9:  
   10: #include "../textbook.h"
   11: #if !defined(Q_MOC_OUTPUT_REVISION)
   12: #error "The header file 'textbook.h' doesn't include <QObject>."
   13: #elif Q_MOC_OUTPUT_REVISION != 61
   14: #error "This file was generated using the moc from 4.5.2. It"
   15: #error "cannot be used with the include files from this version of Qt."
   16: #error "(The moc has changed too much.)"
   17: #endif
   18:  
   19: QT_BEGIN_MOC_NAMESPACE
   20: static const uint qt_meta_data_Textbook[] = {
   21:  
   22:  // content:
   23:        2,       // revision
   24:        0,       // classname
   25:        0,    0, // classinfo
   26:        4,   12, // methods
   27:        4,   32, // properties
   28:        0,    0, // enums/sets
   29:        0,    0, // constructors
   30:  
   31:  // slots: signature, parameters, type, tag, flags
   32:       19,   10,    9,    9, 0x0a,
   33:       45,   37,    9,    9, 0x0a,
   34:       70,   62,    9,    9, 0x0a,
   35:       97,   87,    9,    9, 0x0a,
   36:  
   37:  // properties: name, type, flags
   38:      124,  116, 0x0a095103,
   39:      131,  116, 0x0a095103,
   40:      137,  116, 0x0a095103,
   41:      147,  142, 0x03095003,
   42:  
   43:        0        // eod
   44: };
   45:  
   46: static const char qt_meta_stringdata_Textbook[] = {
   47:     "Textbook\0\0newTitle\0setTitle(QString)\0"
   48:     "newIsbn\0setIsbn(QString)\0newYear\0"
   49:     "setYearPub(uint)\0newAuthor\0"
   50:     "setAuthor(QString)\0QString\0author\0"
   51:     "title\0isbn\0uint\0year\0"
   52: };
   53:  
   54: const QMetaObject Textbook::staticMetaObject = {
   55:     { &QObject::staticMetaObject, qt_meta_stringdata_Textbook,
   56:       qt_meta_data_Textbook, 0 }
   57: };
   58:  
   59: const QMetaObject *Textbook::metaObject() const
   60: {
   61:     return &staticMetaObject;
   62: }
   63:  
   64: void *Textbook::qt_metacast(const char *_clname)
   65: {
   66:     if (!_clname) return 0;
   67:     if (!strcmp(_clname, qt_meta_stringdata_Textbook))
   68:         return static_cast<void*>(const_cast< Textbook*>(this));
   69:     return QObject::qt_metacast(_clname);
   70: }
   71:  
   72: int Textbook::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
   73: {
   74:     _id = QObject::qt_metacall(_c, _id, _a);
   75:     if (_id < 0)
   76:         return _id;
   77:     if (_c == QMetaObject::InvokeMetaMethod) {
   78:         switch (_id) {
   79:         case 0: setTitle((*reinterpret_cast< const QString(*)>(_a[1]))); break;
   80:         case 1: setIsbn((*reinterpret_cast< const QString(*)>(_a[1]))); break;
   81:         case 2: setYearPub((*reinterpret_cast< uint(*)>(_a[1]))); break;
   82:         case 3: setAuthor((*reinterpret_cast< const QString(*)>(_a[1]))); break;
   83:         default: ;
   84:         }
   85:         _id -= 4;
   86:     }
   87: #ifndef QT_NO_PROPERTIES
   88:       else if (_c == QMetaObject::ReadProperty) {
   89:         void *_v = _a[0];
   90:         switch (_id) {
   91:         case 0: *reinterpret_cast< QString*>(_v) = getAuthor(); break;
   92:         case 1: *reinterpret_cast< QString*>(_v) = getTitle(); break;
   93:         case 2: *reinterpret_cast< QString*>(_v) = getIsbn(); break;
   94:         case 3: *reinterpret_cast< uint*>(_v) = getYearPub(); break;
   95:         }
   96:         _id -= 4;
   97:     } else if (_c == QMetaObject::WriteProperty) {
   98:         void *_v = _a[0];
   99:         switch (_id) {
  100:         case 0: setAuthor(*reinterpret_cast< QString*>(_v)); break;
  101:         case 1: setTitle(*reinterpret_cast< QString*>(_v)); break;
  102:         case 2: setIsbn(*reinterpret_cast< QString*>(_v)); break;
  103:         case 3: setYearPub(*reinterpret_cast< uint*>(_v)); break;
  104:         }
  105:         _id -= 4;
  106:     } else if (_c == QMetaObject::ResetProperty) {
  107:         _id -= 4;
  108:     } else if (_c == QMetaObject::QueryPropertyDesignable) {
  109:         _id -= 4;
  110:     } else if (_c == QMetaObject::QueryPropertyScriptable) {
  111:         _id -= 4;
  112:     } else if (_c == QMetaObject::QueryPropertyStored) {
  113:         _id -= 4;
  114:     } else if (_c == QMetaObject::QueryPropertyEditable) {
  115:         _id -= 4;
  116:     } else if (_c == QMetaObject::QueryPropertyUser) {
  117:         _id -= 4;
  118:     }
  119: #endif // QT_NO_PROPERTIES
  120:     return _id;
  121: }
  122: QT_END_MOC_NAMESPACE

Which we can analyze with a function such as …

    1: QString objToString(const QObject* obj) {
    2:     QStringList result;
    3:     const QMetaObject* meta = obj->metaObject();
    4:     result += "\n";
    5:     result += QString("Properties: %1").arg(meta->propertyCount());
    6:     result += QString("Property Macro methods: %1").arg(meta->methodCount());
    7:     result += QString("class %1 : public %2 {").arg(meta->className())
    8:         .arg(meta->superClass()->className());
    9:     for (int i=0; i < meta->propertyCount(); ++i) {
   10:         const QMetaProperty qmp = meta->property(i);
   11:         QVariant value = obj->property(qmp.name());
   12:         result += QString("  %1 %2 = %3;")
   13:             .arg(qmp.type())
   14:             .arg(qmp.name())
   15:             .arg(value.toString());
   16:     }
   17:     result += "};";
   18:     return result.join("\n");
   19: }

Giving us this result …

    1: " 
    2: Properties: 5
    3: Property Macro methods: 8
    4: class Textbook : public QObject {
    5: 10 objectName = 0201700735;
    6: 10 author = Stroustrup;
    7: 10 title = The C++ Programming Language;
    8: 10 isbn = 0201700735;
    9: 3 year = 1997;
   10: };"

Note that we have eight property macro methods, which are the four getters and the four setters we defined. We also have five properties … why five and not four? We have our four properties and the “name” property that is defined within QObject.

If you are working in Visual Studio 2010, as I am, you may want to look at the following information which is kind-of-related:

Yippee, next time we will look at more design patterns and summarize all of the patterns we have encountered during our UNISA adventure this year :)