C++ QT Meta-Object System: Accessing Class Properties and Methods

1. Introduction to the Meta-Object System

The Qt Meta-Object System provides runtime type information and reflection capabilities for <span><span>QObject</span></span> derived classes, including:

  • Names, types, and read/write capabilities of properties (<span><span>Q_PROPERTY</span></span>);

  • Signatures and parameter types of methods (including slots and signals, <span><span>Q_INVOKABLE</span></span>);

  • Names and entries of enumerations (<span><span>Q_ENUM</span></span> / <span><span>Q_ENUMS</span></span>);

  • Runtime method invocation (<span><span>QMetaObject::invokeMethod</span></span>) and accessing properties using <span><span>QVariant</span></span> (<span><span>QObject::property/setProperty</span></span>);

  • Connections and name lookups for signals/slots.

The core entry point is <span><span>QMetaObject</span></span> (each class with <span><span>Q_OBJECT</span></span> generates a static <span><span>metaObject()</span></span>) and <span><span>QMetaProperty</span></span> / <span><span>QMetaMethod</span></span> / <span><span>QMetaEnum</span></span>, etc.

2. Common Classes and APIs

QMetaObject Class

// Get class name
const char* className() const;
// Get parent class's meta-object
const QMetaObject* superClass() const;
// Method related
int methodCount() const;
int indexOfMethod(const char* method) const;
int indexOfSignal(const char* signal) const;
int indexOfSlot(const char* slot) const;
QMetaMethod method(int index) const;
// Property related
int propertyCount() const;
int indexOfProperty(const char* name) const;
QMetaProperty property(int index) const;
// Enumeration related
int enumeratorCount() const;
int indexOfEnumerator(const char* name) const;
QMetaEnumerator enumerator(int index) const;
// Class information
int classInfoCount() const;
int indexOfClassInfo(const char* name) const;
QMetaClassInfo classInfo(int index) const;

QMetaMethod Class

// Method type
QMetaMethod::MethodType methodType() const;
// Method signature
QByteArray methodSignature() const;
QByteArray name() const;
// Parameter types
int parameterCount() const;
QList<QByteArray> parameterTypes() const;
QList<QByteArray> parameterNames() const;
// Return type
QByteArray typeName() const;
int returnType() const;
// Invoke method
bool invoke(QObject* object,             Qt::ConnectionType type,            QGenericReturnArgument ret,            QGenericArgument val0 = QGenericArgument(nullptr),            QGenericArgument val1 = QGenericArgument(),            ...) const;

QMetaProperty Class

// Property information
const char* name() const;
QVariant::Type type() const;
const char* typeName() const;
// Read/write capabilities
bool isReadable() const;
bool isWritable() const;
bool isResettable() const;
// Access property value
QVariant read(const QObject* object) const;
bool write(QObject* object, const QVariant& value) const;
void reset(QObject* object) const;
// Notify signal
QMetaMethod notifySignal() const;
bool hasNotifySignal() const;

QMetaEnum Class

// Enumeration information
const char* name() const;
const char* scope() const;
// Key-value pairs
int keyCount() const;
const char* key(int index) const;
int value(int index) const;
// Convert value to string
const char* valueToKey(int value) const;
int keyToValue(const char* key) const;

Dynamic Properties

// Set and get dynamic properties
void setProperty(const char* name, const QVariant& value);
QVariant property(const char* name) const;
// Check dynamic properties
bool dynamicPropertyNames() const;

Signal-Slot Connections

// Static connection
QMetaObject::Connection connect(const QObject* sender, const char* signal,                               const QObject* receiver, const char* method,                               Qt::ConnectionType type = Qt::AutoConnection);
// Disconnect
void disconnect(const QObject* sender, const char* signal,                const QObject* receiver, const char* method);
// Connection based on QMetaMethod
QMetaObject::Connection connect(const QObject* sender, const QMetaMethod& signal,                               const QObject* receiver, const QMetaMethod& method,                               Qt::ConnectionType type = Qt::AutoConnection);

Dynamic Method Invocation

// Using QMetaMethod to invoke
QMetaMethod method = ...;
QGenericReturnArgument ret;
QGenericArgument arg0;
method.invoke(object, Qt::AutoConnection, ret, arg0);
// Using QMetaObject to invoke
QMetaObject::invokeMethod(object, "methodName",                         Qt::AutoConnection,                         Q_RETURN_ARG(QString, result),                         Q_ARG(int, 123));

Constructing Objects

// Create instance via meta-object
QObject* object = metaObject->newInstance();
// Constructor with parameters
QObject* object = metaObject->newInstance(Q_ARG(int, 100),                                          Q_ARG(QString, "test"));

3. ExampleHeader File MyWidget.h

// MyWidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QMetaEnum>
class MyWidget : public QWidget {
    Q_OBJECT
    Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
    Q_PROPERTY(int value READ value WRITE setValue RESET resetValue NOTIFY valueChanged)
    Q_CLASSINFO("Author", "QtUser")
    Q_CLASSINFO("Version", "1.0")
public:
    explicit MyWidget(QWidget* parent = nullptr);
    QString title() const { return m_title; }
    void setTitle(const QString& t) {
        if (m_title != t) {
            m_title = t;
            emit titleChanged(m_title);
        }
    }
    int value() const { return m_value; }
    void setValue(int v) {
        if (m_value != v) {
            m_value = v;
            emit valueChanged(m_value);
        }
    }
    void resetValue() { m_value = 0; }
signals:
    void titleChanged(const QString& newTitle);
    void valueChanged(int newValue);
public slots:
    void doSomething(int x, const QString& msg);
    QString compute(int a, int b);
private:
    QString m_title = "Default";
    int m_value = 0;
public:
    enum Status {
        Idle = 0,
        Running = 1,
        Finished = 2
    };
    Q_ENUM(Status)
};
#endif // MYWIDGET_H

Source Code MyWidget.cpp

// MyWidget.cpp
#include "MyWidget.h"
#include <QDebug>
MyWidget::MyWidget(QWidget* parent) : QWidget(parent) {}
void MyWidget::doSomething(int x, const QString& msg) {
    qDebug() << "doSomething called with:" << x << "," << msg;
}
QString MyWidget::compute(int a, int b) {
    return QString("Result: %1").arg(a + b);
}

<span><span>QMetaObject</span></span> Related API Examples

<span>className()</span>

const QMetaObject* meta = obj->metaObject();
qDebug() << "Class name:" << meta->className(); // Output: "MyWidget"

<span>superClass()</span>

const QMetaObject* super = meta->superClass();
qDebug() << "Super class:" << super->className(); // Output: "QWidget"

<span>methodCount()</span> / <span>method(index)</span>

qDebug() << "Total methods:" << meta->methodCount(); // Includes signals, slots, invokable, constructors, etc.
for (int i = 0; i < meta->methodCount(); ++i) {
    QMetaMethod m = meta->method(i);
    qDebug() << i << ":" << m.name() << "(" << m.methodSignature() << ");";
}// Output similar to:
// 0 : destroyed (destroyed(QObject*))
// 1 : objectNameChanged (objectNameChanged(QString))
// ...
// N : doSomething (doSomething(int,QString))
// N+1 : compute (compute(int,int))

<span>indexOfMethod()</span>, <span>indexOfSignal()</span>, <span>indexOfSlot()</span>

int idx1 = meta->indexOfMethod("doSomething(int,QString)");
int idx2 = meta->indexOfSignal("valueChanged(int)");
int idx3 = meta->indexOfSlot("doSomething(int,QString)");
qDebug() << "doSomething index:" << idx1;     // >=0
qDebug() << "valueChanged signal index:" << idx2; // >=0
qDebug() << "doSomething slot index:" << idx3;    // Same as idx1 (because it's a slot)

<span>propertyCount()</span> / <span>property(index)</span> / <span>indexOfProperty()</span>

qDebug() << "Property count:" << meta->propertyCount();
int titleIdx = meta->indexOfProperty("title");
if (titleIdx != -1) {
    QMetaProperty prop = meta->property(titleIdx);
    qDebug() << "Property 'title' type:" << prop.typeName();    // Output: "QString"
}

<span>enumeratorCount()</span> / <span>enumerator(index)</span> / <span>indexOfEnumerator()</span>

int enumIdx = meta->indexOfEnumerator("Status");
if (enumIdx != -1) {
    QMetaEnum enu = meta->enumerator(enumIdx);
    qDebug() << "Enum name:" << enu.name(); // "Status"
    qDebug() << "Key count:" << enu.keyCount(); // 3
    for (int i = 0; i < enu.keyCount(); ++i) {
        qDebug() << enu.key(i) << "=" << enu.value(i);
    }
    // Output:
    // "Idle" = 0
    // "Running" = 1
    // "Finished" = 2
    qDebug() << "Value 1 -> key:" << enu.valueToKey(1); // "Running"
    qDebug() << "Key 'Finished' -> value:" << enu.keyToValue("Finished"); // 2
}

<span>classInfoCount()</span> / <span>classInfo(index)</span> / <span>indexOfClassInfo()</span>

int authorIdx = meta->indexOfClassInfo("Author");
if (authorIdx != -1) {
    QMetaClassInfo info = meta->classInfo(authorIdx);
    qDebug() << "ClassInfo -" << info.name() << "=" << info.value();    // Output: "Author = QtUser"
}

<span><span>QMetaMethod</span></span> Example

Assuming we retrieve the <span>compute</span> method:

int methodIdx = meta->indexOfMethod("compute(int,int)");
QMetaMethod method = meta->method(methodIdx);
qDebug() << "Method name:" << method.name();           // "compute"
qDebug() << "Signature:" << method.methodSignature();  // "compute(int,int)"
qDebug() << "Return type:" << method.typeName();       // "QString"
qDebug() << "Parameter count:" << method.parameterCount(); // 2
qDebug() << "Param types:" << method.parameterTypes(); // ("int", "int")
qDebug() << "Param names:" << method.parameterNames(); // ("a", "b")
qDebug() << "Method type:" << method.methodType();     // QMetaMethod::Slot (or Method)
// Invoke method
QString result;
// obj is the instance object
method.invoke(obj, Qt::DirectConnection,              Q_RETURN_ARG(QString, result),              Q_ARG(int, 5), Q_ARG(int, 3));
qDebug() << "Invoked result:" << result; // "Result: 8"

<span>QMetaProperty</span> Example

int propIdx = meta->indexOfProperty("value");
QMetaProperty prop = meta->property(propIdx);
qDebug() << "Property name:" << prop.name();          // "value"
qDebug() << "Type:" << prop.typeName();               // "int"
qDebug() << "Readable:" << prop.isReadable();         // true
qDebug() << "Writable:" << prop.isWritable();         // true
qDebug() << "Resettable:" << prop.isResettable();     // true
// Read/Write
prop.write(obj, 42);
QVariant val = prop.read(obj);
qDebug() << "Read value:" << val.toInt(); // 42
prop.reset(obj);
qDebug() << "After reset:" << prop.read(obj).toInt(); // 0
// Notify signal
if (prop.hasNotifySignal()) {
    QMetaMethod sig = prop.notifySignal();
    qDebug() << "Notify signal:" << sig.name(); // "valueChanged"
}

Dynamic Properties

obj->setProperty("dynamicColor", "red");
obj->setProperty("priority", 5);
QVariant color = obj->property("dynamicColor");
QVariant prio = obj->property("priority");
qDebug() << "Dynamic color:" << color.toString(); // "red"
qDebug() << "Priority:" << prio.toInt();          // 5
// Get all dynamic property names
QList<QByteArray> dynNames = obj->dynamicPropertyNames();
for (const QByteArray& name : dynNames) {
    qDebug() << "Dynamic prop:" << name << "=" << obj->property(name);
}

Signal-Slot Connections (Meta-Object Method)

Static String Connection (Old Style, Not Recommended but Compatible)

// Connect valueChanged -> doSomething (requires parameter matching)
connect(obj, SIGNAL(valueChanged(int)),        obj, SLOT(doSomething(int,QString))); // ❌ Parameters do not match! Will not connect successfully
// Correct example: using lambda or adapter
connect(obj, &MyWidget::valueChanged, obj, [=](int v) {    obj->doSomething(v, "from signal");});
// Connection based on QMetaMethod (Advanced Usage)
QMetaMethod signal = meta->method(meta->indexOfSignal("valueChanged(int)"));
QMetaMethod slot = meta->method(meta->indexOfSlot("doSomething(int,QString)"));
// Note: Parameters must be compatible! Here they are not, just for syntax demonstration
// In practice, it is recommended to use new-style connect or invokeMethod
// Dynamic method invocation using QMetaObject::invokeMethod
// Call void slot
QMetaObject::invokeMethod(obj, "doSomething",                          Qt::DirectConnection,                          Q_ARG(int, 100),                          Q_ARG(QString, "Hello"));
// Call method with return value
QString res;
QMetaObject::invokeMethod(obj, "compute",                          Qt::DirectConnection,                          Q_RETURN_ARG(QString, res),                          Q_ARG(int, 10),                          Q_ARG(int, 20));
qDebug() << "Computed via invokeMethod:" << res; // "Result: 30"
// Constructing Objects (newInstance) Modify MyWidget constructor:
// MyWidget.h
explicit MyWidget(int val, const QString& title, QWidget* parent = nullptr);
Q_INVOKABLE explicit MyWidget(int val, const QString& title);
// MyWidget.cpp
MyWidget::MyWidget(int val, const QString& title, QWidget* parent)    : QWidget(parent), m_value(val), m_title(title) {}
// Overloaded (for newInstance)
MyWidget::MyWidget(int val, const QString& title) : MyWidget(val, title, nullptr) {}
// Then:
const QMetaObject* mo = &MyWidget::staticMetaObject;
QObject* newObj = mo->newInstance(Q_ARG(int, 99), Q_ARG(QString, "New Instance"));
if (newObj) {
    MyWidget* w = qobject_cast<MyWidget*>(newObj);
    qDebug() << "New instance title:" << w->title(); // "New Instance"
    qDebug() << "New instance value:" << w->value(); // 99
    delete w;
} else {
    qDebug() << "Failed to create instance!";
}

Leave a Comment