12.6.  MetaTypes, Declaring and Registering

[ fromfile: reflection.xml id: qmetatype ]

QMetaType is a helper class for working with value types. For more than 60 built-in types, QMetaType associates a type name to a type ID, enabling construction and destruction to occur dynamically at runtime. There is a public enum named QMetaType::Type that has values for all the QVariant compatible types. The enumerator values in QMetaType::Type agree with those in QVariant::Type.

We have added custom enumerated types to the QVariant system, through the use of the Q_ENUMS macro. It is also possible to add value types of our own to the QMetaType list using the macro Q_DECLARE_METATYPE(MyType). If MyType has public default and copy constructors and a public destructor, the Q_DECLARE_METATYPE macro enables it to be used as a custom type in QVariant.

In Example 12.13, we introduce a new value type, Fraction to the known metatypes of the program that contains its definition. We did not need to explicitly define default and copy constructors or a destructor because the compiler-generated ones, which do memberwise copy or assignment, are exactly what we need for this class. Placing the macro in the header file below the class definition is standard practice.

Example 12.13. src/metatype/fraction.h

[ . . . . ]
class Fraction : public QPair<qint32, qint32> {
public:
    Fraction(qint32 n = 0, qint32 d = 1) : QPair<qint32,qint32>(n,d)
    { }
};

Q_DECLARE_METATYPE(Fraction);
[ . . . . ]

qRegisterMetaType()

A registered metatype must be already declared with Q_DECLARE_METATYPE. The template function qRegisterMetaType<T>() registers the type T and returns the internal ID used by QMetaType. There is an overloaded version of this function, qRegisterMetaType<T>(const char* name), that enables you to register name as the name of type T. The call to this function must occur early in the main program, before any attempt is made to use the registered type in a dynamic way.

When a metatype is declared, it is possible to store a value in a QVariant. Example 12.14 shows how to store and get declared metatype values back from a QVariant.

Example 12.14. src/metatype/metatype.cpp

[ . . . . ]

int main (int argc, char* argv[]) {
    QApplication app(argc, argv);
    qRegisterMetaType<Fraction>("Fraction");
    Fraction twoThirds (2,3);
    QVariant var;
    var.setValue(twoThirds);
    Q_ASSERT (var.value<Fraction>() == twoThirds);

    Fraction oneHalf (1,2);
    Fraction threeQuarters (3,4);

    qDebug() << "QList<Fraction> to QVariant and back."

    QList<Fraction> fractions;
    fractions << oneHalf << twoThirds << threeQuarters;
    QFile binaryTestFile("testMetaType.bin");
    binaryTestFile.open(QIODevice::WriteOnly);
    QDataStream dout(&binaryTestFile);
    dout << fractions;
    binaryTestFile.close();
    binaryTestFile.open(QIODevice::ReadOnly);
    QDataStream din(&binaryTestFile);
    QList<Fraction> frac2;
    din >> frac2;
    binaryTestFile.close();
    Q_ASSERT(fractions == frac2);
    createTest();
    qDebug() << "If this output appears, all tests passed.";
}

Values of a registered type can be constructed dynamically via QMetaType::construct(), as shown in Example 12.15.

Example 12.15. src/metatype/metatype.cpp

[ . . . . ]

void createTest() {
    static int fracType = QMetaType::type("Fraction");
    void* vp = QMetaType::construct(fracType);
    Fraction* fp = reinterpret_cast<Fraction*>(vp); 1
    fp->first = 1;
    fp->second = 2;
    Q_ASSERT(*fp == Fraction(1,2));
}

1

Note: This is our first use of reinterpret_cast in this book!


QMetaType::construct() returns a void pointer, so we use reinterpret_cast to convert it to a Fraction pointer.