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.

Mermaid diagram

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