Search code examples
c++qteventsobserver-patternsignals-slots

Why are signals and slots better than plain old callbacks?


Newbie to C++ here. I was reading A Deeper Look at Signals and Slots, which claims that 1) callbacks are inherently type-unsafe, and 2) to make them safe you need to define a pure virtual class wrapper around your function. I'm having a hard time understanding why that's true. As an example, here is the code Qt provides on their tutorial page for signals and slots:

// Header file
#include <QObject>

class Counter : public QObject
{
    Q_OBJECT

public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

// .cpp file
void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

// Later on...
Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)),
                 &b, SLOT(setValue(int)));

a.setValue(12);     // a.value() == 12, b.value() == 12
b.setValue(48);     // a.value() == 12, b.value() == 48

Here is that code rewritten using callbacks:

#include <functional>
#include <vector>

class Counter
{
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }
    std::vector<std::function<void(int)>> valueChanged;

    void setValue(int value);

private:
    int m_value;
};

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        for (auto func : valueChanged) {
            func(value);
        }
    }
}

// Later on...
Counter a, b;
auto lambda = [&](int value) { b.setValue(value); };
a.valueChanged.push_back(lambda);

a.setValue(12);
b.setValue(48);

As you can see, the callback version is type-safe and shorter than the Qt version, despite them claiming that it's not. It does not define any new classes, aside from Counter. It uses only standard library code and doesn't need a special compiler (moc) to work. Why, then, are signals and slots preferred over callbacks? Has C++11 simply obsoleted these concepts?

Thanks.


Solution

  • Why are signals and slots better than plain old callbacks?

    Because signals are a lot like plain old callbacks, on top of having extra features and being deeply integrated with Qt APIs. It ain't rocket science - callbacks + extra features + deep integration is greater than callbacks alone. C++ might be finally offering a cleaner way to do callbacks, but that doesn't replace Qt signals and slots, much less render them obsolete.

    The slot aspect got a little less relevant since Qt 5, which allowed signals to be connected to any functions. But still, slots integrate with the Qt meta system, which is used by a lot of Qt APIs to get things working.

    Yes, you could use callbacks for pretty much everything which signals are supposed to achieve. But it is not easier, it is a little more verbose, it doesn't automatically handle queued connections, it won't integrate with Qt the way signals do, you could probably work around that as well, but it will get even more verbose.

    And in the case of QML, which nowadays is the primary focus of Qt, you are essentially stuck with Qt's signals. So I presume signals are here to stay.

    Signals and slots are "better" because Qt is conceptually built around them, they are part of the API and are used by a lot of the APIs. Those concepts have been in Qt for a long time, from back in the days C++ didn't offer much callback support aside from plain old function pointers it inherited from C. This is also the reason Qt cannot simply switch to std callbacks - it will break a lot of stuff and is a needless effort. The same reason Qt continues to use those evil unsafe plain old pointers instead of smart pointers. Signals and slots are not obsolete as a concept, even less so technically when using Qt. C++ simply got too late in the game. It is unrealistic to expect that everyone will now rush into moving away from their own implementations in their giant code bases now that C++ finally provides alternatives as part of the language standard library.