What Are Signals & Slots?
Signals and slots are Qt’s mechanism for loose-coupled communication between objects. A signal is emitted when something happens; a slot is a function that responds to that signal. No direct dependency between sender and receiver.
[Sender Object] --signal--> [Receiver Object]
button clicked handler()
Declaring Signals & Slots
A class must inherit from QObject and include the Q_OBJECT macro:
// counter.h
#include <QObject>
class Counter : public QObject {
Q_OBJECT
public:
explicit Counter(QObject *parent = nullptr);
int value() const { return m_value; }
public slots:
void setValue(int value);
void increment();
signals:
void valueChanged(int newValue);
private:
int m_value = 0;
};
// counter.cpp
#include "counter.h"
Counter::Counter(QObject *parent) : QObject(parent) {}
void Counter::setValue(int value) {
if (value != m_value) {
m_value = value;
emit valueChanged(m_value); // emit the signal
}
}
void Counter::increment() {
setValue(m_value + 1);
}
Connecting Signals to Slots
Function Pointer Syntax (Qt 5+, recommended)
Counter a, b;
// Connect a's signal to b's slot
QObject::connect(&a, &Counter::valueChanged,
&b, &Counter::setValue);
a.setValue(10);
// b.value() is now 10
Lambda Slot
Counter counter;
QObject::connect(&counter, &Counter::valueChanged,
[](int value) {
qDebug() << "Value changed to:" << value;
});
counter.setValue(42);
// Output: Value changed to: 42
Old String-Based Syntax (avoid in new code)
// Legacy — no compile-time type checking
connect(&a, SIGNAL(valueChanged(int)),
&b, SLOT(setValue(int)));
Built-in Widget Signals
Qt widgets come with many built-in signals:
#include <QApplication>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
QVBoxLayout *layout = new QVBoxLayout(&window);
QPushButton *btn = new QPushButton("Click Me");
QLabel *label = new QLabel("Waiting...");
layout->addWidget(btn);
layout->addWidget(label);
int clickCount = 0;
QObject::connect(btn, &QPushButton::clicked, [&]() {
clickCount++;
label->setText(QString("Clicked %1 times").arg(clickCount));
});
window.show();
return app.exec();
}
Signal-to-Signal Connections
Signals can be chained — one signal triggers another:
QPushButton *btn = new QPushButton("Quit");
QObject::connect(btn, &QPushButton::clicked,
&app, &QApplication::quit);
Thread-Safe Signal Emission
When sender and receiver are in different threads, Qt uses queued connections automatically:
// Explicit queued connection (cross-thread)
QObject::connect(&worker, &Worker::resultReady,
&ui, &UI::displayResult,
Qt::QueuedConnection);
// Auto-detect (default): uses queued if different threads
QObject::connect(&worker, &Worker::resultReady,
&ui, &UI::displayResult,
Qt::AutoConnection);
Connection types:
| Type | Behavior |
|---|---|
Qt::AutoConnection | Direct if same thread, queued if different |
Qt::DirectConnection | Slot called immediately in sender’s thread |
Qt::QueuedConnection | Slot called in receiver’s thread via event loop |
Qt::BlockingQueuedConnection | Queued + blocks sender until slot returns |
Disconnecting
// Disconnect a specific connection
QMetaObject::Connection conn = QObject::connect(...);
QObject::disconnect(conn);
// Disconnect all signals from an object
QObject::disconnect(&sender, nullptr, nullptr, nullptr);
// Disconnect a specific signal from a receiver
QObject::disconnect(&sender, &Sender::mySignal,
&receiver, &Receiver::mySlot);
Summary
| Concept | Key Point |
|---|---|
signals: | Declare with no implementation — Qt generates them |
slots: | Regular member functions callable via connect |
emit | Keyword to fire a signal |
Q_OBJECT | Required macro for MOC processing |
| Lambda slots | Clean, concise — capture with [&] carefully |
| Thread safety | Use Qt::QueuedConnection across threads |
Advanced Topics
1. Custom Signal with Multiple Parameters
class GPSSensor : public QObject {
Q_OBJECT
signals:
void locationUpdated(double lat, double lon, float accuracy);
};
// Connect
QObject::connect(&gps, &GPSSensor::locationUpdated,
[](double lat, double lon, float acc) {
qDebug() << "GPS:" << lat << lon << "±" << acc << "m";
});
2. Overloaded Signals — Use QOverload
class Display : public QObject {
Q_OBJECT
signals:
void updated(int value);
void updated(const QString &text);
};
// Must resolve overload explicitly
QObject::connect(&display,
QOverload<int>::of(&Display::updated),
[](int v){ qDebug() << "Int:" << v; });
3. Blocking Connection (Cross-Thread Sync)
// Blocks caller thread until slot in receiver thread completes
QObject::connect(&worker, &Worker::dataReady,
&db, &Database::store,
Qt::BlockingQueuedConnection);
4. Connection Guard (RAII disconnect)
QMetaObject::Connection conn =
QObject::connect(&btn, &QPushButton::clicked, handler);
// Later: disconnect cleanly
QObject::disconnect(conn);
5. Signal Spy (for testing)
#include <QSignalSpy>
QSignalSpy spy(&counter, &Counter::valueChanged);
counter.setValue(5);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).at(0).toInt(), 5);
Visual: Connection Types
┌─────────────────────────────────────────────────────────┐
│ Qt Connection Types │
├─────────────────┬───────────────────────────────────────┤
│ AutoConnection │ Same thread → Direct │
│ │ Diff thread → Queued │
├─────────────────┼───────────────────────────────────────┤
│ Direct │ Slot runs inline in sender's thread │
├─────────────────┼───────────────────────────────────────┤
│ Queued │ Event posted → slot in receiver thread│
├─────────────────┼───────────────────────────────────────┤
│ BlockingQueued │ Queued + sender thread blocks │
├─────────────────┼───────────────────────────────────────┤
│ UniqueConn │ Prevents duplicate connections │
└─────────────────┴───────────────────────────────────────┘
Lab 1 — Temperature Monitor
Goal: Connect a sensor that emits temperature readings to a display widget.
// sensor.h
class TemperatureSensor : public QObject {
Q_OBJECT
public:
void simulate() {
for (int i = 0; i < 5; ++i) {
float temp = 20.0f + (rand() % 100) / 10.0f;
emit temperatureRead(temp);
QThread::msleep(500);
}
}
signals:
void temperatureRead(float celsius);
};
// display.h
class TempDisplay : public QObject {
Q_OBJECT
public slots:
void showTemperature(float c) {
qDebug() << QString("Temp: %1 °C").arg(c, 0, 'f', 1);
}
};
// main.cpp
TemperatureSensor sensor;
TempDisplay display;
QObject::connect(&sensor, &TemperatureSensor::temperatureRead,
&display, &TempDisplay::showTemperature);
sensor.simulate();
Expected Output:
Temp: 23.4 °C
Temp: 27.1 °C
Temp: 21.8 °C
...
Lab 2 — Event Bus Pattern
Goal: Implement a central event bus where multiple receivers subscribe to named events.
// eventbus.h
class EventBus : public QObject {
Q_OBJECT
public:
static EventBus& instance() {
static EventBus bus;
return bus;
}
signals:
void deviceConnected(const QString &deviceId);
void alarmTriggered(int level, const QString &msg);
};
// usage
QObject::connect(&EventBus::instance(), &EventBus::alarmTriggered,
[](int level, const QString &msg) {
qDebug() << "[ALARM L" << level << "]" << msg;
});
emit EventBus::instance().alarmTriggered(2, "High temperature!");
Interview Questions
Q1: What is the difference between Qt::DirectConnection and Qt::QueuedConnection?
Direct: slot executes immediately in sender’s thread. Queued: an event is posted to the receiver’s event queue; slot executes in the receiver’s thread. Use Queued for cross-thread safety.
Q2: Why can’t you use old SIGNAL/SLOT macros in new code?
They use string matching at runtime, offer no compile-time type checking, and are slower than function pointer syntax. A typo silently fails at runtime.
Q3: What happens if you connect() the same signal/slot pair twice?
The slot is called twice per emission. Use
Qt::UniqueConnectionflag to prevent duplicates.
Q4: How do you disconnect all connections from an object when it’s destroyed?
Qt does it automatically — when a QObject is destroyed, all connections involving it (as sender or receiver) are automatically cleaned up.
Q5: Can signals have return values?
No. Signals always return
void. For return data, use another signal from the receiver back to the sender, or a shared data structure.
Application: IoT Device Manager
// device_manager.h
class DeviceManager : public QObject {
Q_OBJECT
public:
void addDevice(const QString &id) {
m_devices.append(id);
emit deviceAdded(id);
}
void removeDevice(const QString &id) {
m_devices.removeAll(id);
emit deviceRemoved(id);
}
int count() const { return m_devices.size(); }
signals:
void deviceAdded(const QString &id);
void deviceRemoved(const QString &id);
void countChanged(int newCount);
private:
QStringList m_devices;
};
// logger.h
class Logger : public QObject {
Q_OBJECT
public slots:
void onDeviceAdded(const QString &id) {
qDebug() << "[LOG] Device connected:" << id;
}
void onDeviceRemoved(const QString &id) {
qDebug() << "[LOG] Device disconnected:" << id;
}
};
// main.cpp
DeviceManager mgr;
Logger logger;
QLabel *statusLabel = new QLabel;
QObject::connect(&mgr, &DeviceManager::deviceAdded,
&logger, &Logger::onDeviceAdded);
QObject::connect(&mgr, &DeviceManager::deviceRemoved,
&logger, &Logger::onDeviceRemoved);
QObject::connect(&mgr, &DeviceManager::deviceAdded,
[&](const QString &id) {
statusLabel->setText(QString("%1 device(s) online").arg(mgr.count()));
});
mgr.addDevice("sensor-001");
mgr.addDevice("actuator-002");
mgr.removeDevice("sensor-001");
Output:
[LOG] Device connected: sensor-001
[LOG] Device connected: actuator-002
[LOG] Device disconnected: sensor-001
References
| Resource | Link |
|---|---|
| Qt Docs: Signals & Slots | https://doc.qt.io/qt-6/signalsandslots.html |
| Qt Docs: QObject::connect | https://doc.qt.io/qt-6/qobject.html#connect |
| Qt Docs: Thread Support | https://doc.qt.io/qt-6/threads-qobject.html |
| Qt Docs: QSignalSpy | https://doc.qt.io/qt-6/qsignalspy.html |
Next tutorial → Qt Widgets — Building GUIs