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.
shared_ptr
and lambdas, closures, andstd::function
, it would probably be better and even easier (less boilerplate, more flexibility, and reduced MOC reliance) to use a signals and slots implementation based on standard C++ concepts. But that wasn't true when Qt was originally conceived. – Tinnedvoid*
or a variant or something of that sort as an argument -- and then cast the data back to its original form at the callback site in which the event is handled. That's where the type safety is lost, at least at compile-time, and both compile-time and runtime ifvoid*
is used. – Tinnedvoid*
, then when your function pointer is called, it might have a signature likevoid (*event)(void* user_data)
. You then have to cast theuser_data
to what you passed in earlier (the app data you wanted to access). – Tinnedint
. But the problem is that your lambdas are closures accessing theseCounters
. To design a general API where you can access those counters or anything else from within a C-style function pointer callback, they'd typically have to be squeezed through a void pointer, or you'd have to lose the generality completely and design an API which only works with counters when an event is triggered. Either that or you have to make your counters into global variables. Hope that makes sense! – Tinned