Qt's signals and slots mechanism implements the Observer pattern for you. It uses virtual functions and metaprogramming to give you automatic lifetime management and type-safe connections.
Key advantages over a manual observer
| Aspect | Manual Observer | Qt Signals/Slots |
|---|---|---|
| Lifetime management | Manual; risk of dangling pointers | Automatic; destructor breaks connections |
| Type safety | Runtime check at signal/slot point | Compile-time checking (with Qt 6.0+) |
| Declaration | Virtual function + boilerplate | signals: and slots: keywords |
| Connection syntax | attach(observer*) |
connect(sender, &Class::signal, receiver, &Class::slot) |
| Disconnection | Requires observer pointer | disconnect() or auto on destruction |
| Flexibility | Any observer type | Only QObject-derived classes |
| Callback overhead | Minimal (vtable) | Moderate (string-based, Qt 5; compile-time, Qt 6) |
How signals and slots work
A signal declares an event. It announces that something happened. A slot is a function that responds to a signal. The connect mechanism wires the two together. When a signal is emitted, Qt calls every connected slot.
Emitting button.clicked() calls all connected slots.
The code (Qt 5 style)
#include <QObject>
#include <QApplication>
#include <iostream>
// ── Model (Subject/Emitter) ────────────────────────────────
class DataModel : public QObject {
Q_OBJECT
private:
int value_ = 0;
public:
int get_value() const { return value_; }
void set_value(int new_value) {
if (value_ != new_value) {
value_ = new_value;
emit valueChanged(new_value); // Signal emitted here
}
}
signals: // Declaration only; no implementation
void valueChanged(int new_value);
};
// ── View 1 (Observer) ──────────────────────────────────────
class DisplayWindow : public QObject {
Q_OBJECT
public slots: // Can be called from signals
void on_value_changed(int value) {
std::cout << "[Display] Value: " << value << "\n";
}
};
// ── View 2 (Observer) ──────────────────────────────────────
class LoggingWindow : public QObject {
Q_OBJECT
public slots:
void on_value_changed(int value) {
std::cout << "[Logger] Value: " << value << "\n";
}
};
// ── Alert System (Observer with parameters) ───────────────
class AlertWindow : public QObject {
Q_OBJECT
private:
int threshold_;
public:
explicit AlertWindow(int threshold) : threshold_(threshold) {}
public slots:
void on_value_changed(int value) {
if (value > threshold_) {
std::cout << "[Alert] WARNING: " << value
<< " exceeds " << threshold_ << "\n";
}
}
};
// ── Usage ──────────────────────────────────────────────────
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DataModel model;
DisplayWindow display;
LoggingWindow logger;
AlertWindow alert(50);
// Connect signal to slots
// sender, &Class::signal, receiver, &Class::slot
QObject::connect(&model, &DataModel::valueChanged,
&display, &DisplayWindow::on_value_changed);
QObject::connect(&model, &DataModel::valueChanged,
&logger, &LoggingWindow::on_value_changed);
QObject::connect(&model, &DataModel::valueChanged,
&alert, &AlertWindow::on_value_changed);
std::cout << "Connected 3 slots\n\n";
std::cout << "Setting value to 10:\n";
model.set_value(10);
std::cout << "\nSetting value to 60:\n";
model.set_value(60);
std::cout << "\nDisconnecting logger:\n";
QObject::disconnect(&model, &DataModel::valueChanged,
&logger, &LoggingWindow::on_value_changed);
std::cout << "Setting value to 20:\n";
model.set_value(20);
return 0; // Objects destroyed; all connections auto-cleaned
}
Output:
Connected 3 slots
Setting value to 10:
[Display] Value: 10
[Logger] Value: 10
Setting value to 60:
[Display] Value: 60
[Logger] Value: 60
[Alert] WARNING: 60 exceeds 50
Disconnecting logger:
Setting value to 20:
[Display] Value: 20
[Alert] WARNING: 20 exceeds 50
Signal properties
class DataModel : public QObject {
Q_OBJECT
signals:
// Can pass multiple parameters of any type
void valueChanged(int new_value);
void stateChanged(int id, const QString& state);
// Can even have const parameters
void dataUpdated(const QByteArray& payload);
// Multiple signals from one object
void loading_started();
void loading_finished(bool success);
void progress(int percent);
};
Signals:
- They have no implementation. The moc (Meta-Object Compiler) generates the code.
- They can take multiple parameters of any type.
- A signal can connect to several slots (one-to-many).
- A signal can connect to another signal, forming a chain.
- They cannot return values (fire-and-forget).
Slot properties
class Observer : public QObject {
Q_OBJECT
public slots:
// Must match signal signature (or be convertible)
void on_changed(int value) { }
// Can have fewer parameters than signal (extras ignored)
void on_state_changed() { }
// Slot can be called from code too
void update_ui() { }
// Slots can call other slots and signals
void refresh() { emit refreshed(); }
signals:
void refreshed();
};
Slots: - They are regular functions with an implementation. - They can take fewer parameters than the signal (extra arguments are ignored). - They can be called directly from code, unlike signals. - They can emit signals themselves. - They are virtual and subclasses can override them.
Automatic lifetime management
Unlike a manual observer, Qt disconnects automatically when objects are destroyed:
{
DataModel model;
DisplayWindow* display = new DisplayWindow();
QObject::connect(&model, &DataModel::valueChanged,
display, &DisplayWindow::on_value_changed);
delete display; // ✓ All connections automatically removed
}
// display is deleted; valueChanged() no longer calls its slots
model.set_value(10); // Safe! No dangling pointers.
Qt tracks parent-child relationships through QObject and disconnects signals when an object is destroyed.
Qt 5 vs Qt 6 connect syntax
Qt 5 checks at runtime and uses strings:
QObject::connect(&button, SIGNAL(clicked()),
&window, SLOT(on_button_clicked()));
Qt 6 checks at compile time and uses pointers to members:
QObject::connect(&button, &QPushButton::clicked,
&window, &Window::on_button_clicked);
Qt 6 syntax is preferred because the compiler verifies that the signal and slot exist and are compatible.
When to use signals and slots
Use them for: - GUI applications with buttons, menus, and dialogs - Model-view architectures that decouple data from presentation - Plugin systems where plugins emit signals and the host connects to them - Event-driven systems inside a Qt application - Cross-thread communication, which Qt manages automatically
Avoid them when:
- You are not using Qt (no QObject, no moc).
- Performance is critical and every nanosecond counts.
- You need return values from observers (signals are fire-and-forget).
- You need a simple one-off callback (a lambda with std::function is lighter).
Advantages
- Connections disconnect when the receiver is destroyed.
- Qt 6 checks signal and slot compatibility at compile time.
- The syntax makes the intent clear.
- You don't manage a list of observers.
- Qt handles thread-safe delivery for cross-thread signals.
- One signal can feed many slots, and many signals can feed one slot.
- Qt tools can inspect and trace connections.
Disadvantages
- Only works with QObject and moc.
- More runtime and compile-time machinery than a raw function call.
- Signals are one-way and fire-and-forget, so there are no return values.
- Slots still use virtual calls.
- You need to understand moc and QObject.
Comparison of observer patterns
| Approach | Type Safety | Decoupling | Lifetime Mgmt | Framework | Complexity |
|---|---|---|---|---|---|
| Interface-based | Partial | Manual | Manual | None | Low |
| std::function | None | Good | Manual | None | Medium |
| Qt Signals/Slots | High (Qt6) | Excellent | Automatic | Qt-required | Medium |
| Event System | High | Excellent | Manual | None | High |
Summary
Signals and slots are the Observer pattern with the lifetime rules built in. Use them in Qt code. Outside Qt they are not available, and a smaller callback mechanism is usually the better fit.
Next steps
- Object-oriented event system provides object-oriented events for non-Qt applications with type-safe hierarchies.
- Lifetime management covers lifetime management for manual observer implementations.