What is QObject?
QObject is the base class of almost all Qt classes. It provides:
- Signals & Slots — event-driven communication
- Parent-child memory management — automatic deletion of children
- Properties — runtime-accessible attributes via
Q_PROPERTY - Meta-object system — runtime type information (RTTI)
- Event handling —
event()virtual method
Parent-Child Memory Management
When a QObject has a parent, the parent takes ownership and deletes children automatically when it is destroyed. This prevents memory leaks without needing delete everywhere.
#include <QObject>
#include <QDebug>
class Device : public QObject {
Q_OBJECT
public:
explicit Device(const QString &name, QObject *parent = nullptr)
: QObject(parent), m_name(name)
{
qDebug() << "Device created:" << m_name;
}
~Device() {
qDebug() << "Device destroyed:" << m_name;
}
private:
QString m_name;
};
int main() {
QObject root; // parent
Device *uart = new Device("UART", &root); // child of root
Device *spi = new Device("SPI", &root); // child of root
// When root goes out of scope, uart and spi are deleted automatically
return 0;
}
Output:
Device created: UART
Device created: SPI
Device destroyed: SPI
Device destroyed: UART
Device destroyed: (root)
The Q_OBJECT Macro
Every class that uses signals, slots, or properties must have Q_OBJECT in its class body and inherit from QObject:
class Sensor : public QObject {
Q_OBJECT // <-- required for MOC processing
public:
explicit Sensor(QObject *parent = nullptr);
};
Without Q_OBJECT, signals/slots and connect() won’t work. The Meta-Object Compiler (MOC) reads this macro and generates the required boilerplate.
Q_PROPERTY — Runtime Properties
Q_PROPERTY exposes class members to Qt’s meta-object system, enabling access from QML, stylesheets, and serialization:
class TemperatureSensor : public QObject {
Q_OBJECT
Q_PROPERTY(float temperature READ temperature NOTIFY temperatureChanged)
Q_PROPERTY(QString unit READ unit WRITE setUnit NOTIFY unitChanged)
Q_PROPERTY(bool connected READ isConnected CONSTANT)
public:
explicit TemperatureSensor(QObject *parent = nullptr);
float temperature() const { return m_temperature; }
QString unit() const { return m_unit; }
bool isConnected() const { return m_connected; }
public slots:
void setUnit(const QString &unit) {
if (m_unit != unit) {
m_unit = unit;
emit unitChanged(m_unit);
}
}
void readSensor() {
m_temperature = 36.5f; // read from hardware
emit temperatureChanged(m_temperature);
}
signals:
void temperatureChanged(float temp);
void unitChanged(const QString &unit);
private:
float m_temperature = 0.0f;
QString m_unit = "°C";
bool m_connected = true;
};
Accessing properties dynamically:
TemperatureSensor sensor;
// Dynamic property access via meta-object
QVariant temp = sensor.property("temperature");
qDebug() << "Temperature:" << temp.toFloat();
sensor.setProperty("unit", "°F");
Object Trees
QObject objects form trees. Use children() and findChild() to navigate:
QObject root;
root.setObjectName("root");
QObject *bus = new QObject(&root); bus->setObjectName("I2C_Bus");
QObject *dev1 = new QObject(bus); dev1->setObjectName("Sensor_0x48");
QObject *dev2 = new QObject(bus); dev2->setObjectName("Sensor_0x49");
// Find a child by name
QObject *found = root.findChild<QObject*>("Sensor_0x48");
if (found) qDebug() << "Found:" << found->objectName();
// Find all children of a type
QList<QObject*> all = root.findChildren<QObject*>();
qDebug() << "Total objects:" << all.size();
Runtime Type Information — qobject_cast
Use qobject_cast instead of dynamic_cast for QObject-derived types:
QObject *obj = createDevice(); // returns QObject*
// Safe downcast
if (auto *sensor = qobject_cast<TemperatureSensor*>(obj)) {
qDebug() << "Temp:" << sensor->temperature();
} else {
qDebug() << "Not a TemperatureSensor";
}
qobject_cast returns nullptr on failure (no exceptions) and works without RTTI enabled — important for embedded targets.
QObject — What NOT to Do
// BAD: QObject cannot be copied
QObject a;
QObject b = a; // compile error — copy constructor is deleted
// BAD: stack-allocating a child then giving it a parent
{
QObject child;
QObject parent;
child.setParent(&parent);
} // double-delete! child destroyed by scope AND by parent
// CORRECT: heap-allocate children with parent pointer
QObject parent;
QObject *child = new QObject(&parent); // parent deletes child
Summary
| Feature | Description |
|---|---|
Q_OBJECT | Required for MOC, signals/slots, properties |
| Parent-child | Parent auto-deletes all children |
Q_PROPERTY | Expose members to meta-object system and QML |
findChild() | Navigate object trees at runtime |
qobject_cast | Safe, no-exception downcast for QObjects |
| No copying | QObject is non-copyable by design |
Advanced Topics
Dynamic Properties
QObject obj;
// Set arbitrary properties at runtime — no Q_PROPERTY needed
obj.setProperty("baudRate", 115200);
obj.setProperty("deviceName", "ttyUSB0");
QVariant rate = obj.property("baudRate");
qDebug() << rate.toInt(); // 115200
Object Ownership Patterns
// Pattern 1: Stack root, heap children (most common)
QWidget window; // stack
QLabel *label = new QLabel("Hi", &window); // heap child
// Pattern 2: Shared ownership via QSharedPointer (no parent)
QSharedPointer<QObject> obj(new QObject);
// Pattern 3: Scoped (no parent, auto-delete)
QScopedPointer<QObject> scoped(new QObject);
Custom Meta-Object Info
class Protocol : public QObject {
Q_OBJECT
Q_CLASSINFO("version", "1.2")
Q_CLASSINFO("protocol", "UART/I2C")
public:
explicit Protocol(QObject *parent = nullptr) : QObject(parent) {}
};
// Access at runtime
const QMetaObject *mo = Protocol::staticMetaObject();
for (int i = 0; i < mo->classInfoCount(); ++i) {
qDebug() << mo->classInfo(i).name() << mo->classInfo(i).value();
}
// Output:
// version 1.2
// protocol UART/I2C
Invoking Methods Dynamically
class Actuator : public QObject {
Q_OBJECT
public slots:
void activate() { qDebug() << "Activated!"; }
int getStatus() { return 42; }
};
Actuator act;
// Invoke at runtime — useful for plugins
QMetaObject::invokeMethod(&act, "activate");
int result = 0;
QMetaObject::invokeMethod(&act, "getStatus",
Q_RETURN_ARG(int, result));
qDebug() << result; // 42
Visual: Qt Object Tree
QWidget (root)
│
├── QVBoxLayout
│
├── QLabel ("Title")
│
├── QPushButton ("Connect")
│
└── QFrame (panel)
├── QLineEdit (input)
└── QSpinBox (value)
When QWidget is destroyed → entire tree is deleted automatically
Lab 1 — Sensor Registry with findChild
QObject registry;
registry.setObjectName("SensorRegistry");
// Create named sensors
auto *temp = new QObject(®istry); temp->setObjectName("temp_0x48");
auto *humi = new QObject(®istry); humi->setObjectName("humi_0x50");
auto *pres = new QObject(®istry); pres->setObjectName("pres_0x60");
// Look up by name
QObject *found = registry.findChild<QObject*>("humi_0x50");
if (found)
qDebug() << "Found sensor:" << found->objectName();
// List all sensors
const auto all = registry.findChildren<QObject*>();
qDebug() << "Registered sensors:" << all.size();
for (auto *s : all)
qDebug() << " -" << s->objectName();
Lab 2 — Dynamic Property Config
class DeviceConfig : public QObject {
Q_OBJECT
public:
void loadDefaults() {
setProperty("baud", 9600);
setProperty("parity", "none");
setProperty("databits", 8);
setProperty("stopbits", 1);
}
void printAll() {
const auto names = dynamicPropertyNames();
for (const auto &n : names)
qDebug() << n << "=" << property(n.constData()).toString();
}
};
DeviceConfig cfg;
cfg.loadDefaults();
cfg.setProperty("baud", 115200); // override one
cfg.printAll();
// baud = 115200
// parity = none
// databits = 8
// stopbits = 1
Interview Questions
Q1: Why is QObject non-copyable?
QObject has an object identity — it may have connections, a parent, children, and a thread affinity. Copying would create ambiguous ownership and duplicate connections. The copy constructor and assignment operator are deleted.
Q2: What does MOC do?
The Meta-Object Compiler (MOC) reads C++ header files and generates a
moc_*.cppfile containing the implementation of signals, themetaObject()vtable entry, andqt_metacalldispatcher. It enables runtime introspection without RTTI.
Q3: What’s the difference between Q_PROPERTY with READ/WRITE vs MEMBER?
READ/WRITErequires getter/setter functions.MEMBERdirectly binds to a class member variable — Qt generates the getter/setter automatically.MEMBERis concise;READ/WRITEallows custom logic.
Q4: When should you use deleteLater() instead of delete?
When inside a slot or signal handler — calling
delete thiscan crash if the event loop still holds a reference.deleteLater()schedules deletion after the current event loop iteration completes safely.
Q5: How does qobject_cast differ from dynamic_cast?
qobject_castuses Qt’s meta-object system and works without RTTI (important for embedded). It returnsnullptron failure.dynamic_castrequires RTTI and throws exceptions on some compilers.
Application: Plugin-Style Device Registry
// idevice.h — interface
class IDevice : public QObject {
Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId CONSTANT)
public:
explicit IDevice(const QString &id, QObject *parent = nullptr)
: QObject(parent), m_id(id) {}
QString deviceId() const { return m_id; }
virtual void initialize() = 0;
virtual QString status() const = 0;
signals:
void statusChanged(const QString &newStatus);
private:
QString m_id;
};
// uart_device.h
class UartDevice : public IDevice {
Q_OBJECT
public:
UartDevice(QObject *p=nullptr) : IDevice("uart-0", p) {}
void initialize() override { qDebug() << "UART init"; }
QString status() const override { return "running"; }
};
// registry.h
class DeviceRegistry : public QObject {
Q_OBJECT
public:
void registerDevice(IDevice *dev) {
dev->setParent(this); // registry owns device
m_devices[dev->deviceId()] = dev;
QObject::connect(dev, &IDevice::statusChanged,
[this, dev](const QString &s) {
qDebug() << dev->deviceId() << "→" << s;
});
}
IDevice* find(const QString &id) {
return m_devices.value(id, nullptr);
}
void initAll() {
for (auto *d : m_devices) d->initialize();
}
private:
QHash<QString, IDevice*> m_devices;
};
// main.cpp
DeviceRegistry registry;
registry.registerDevice(new UartDevice);
if (auto *dev = qobject_cast<UartDevice*>(registry.find("uart-0")))
qDebug() << "Status:" << dev->status();
registry.initAll();
References
| Resource | Link |
|---|---|
| Qt Docs: QObject | https://doc.qt.io/qt-6/qobject.html |
| Qt Docs: Object Trees | https://doc.qt.io/qt-6/objecttrees.html |
| Qt Docs: Q_PROPERTY | https://doc.qt.io/qt-6/properties.html |
| Qt Docs: Meta-Object System | https://doc.qt.io/qt-6/metaobjects.html |
Next tutorial → QML & Qt Quick