为什么信号和插槽比普通的旧回调更好?

时间:2015-12-12 06:52:38

标签: c++ qt events observer-pattern signals-slots

新手到C ++这里。我正在阅读A Deeper Look at Signals and Slots,它声称1)回调本质上是类型不安全的,2)为了使它们安全,你需要在你的函数周围定义一个纯虚拟类包装器。我很难理解为什么这是真的。例如,以下是Qt在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

以下是使用回调重写的代码:

#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);

如您所见,回调版本是类型安全的,并且比Qt版本despite them claiming that it's not更短。除了Counter之外,它没有定义任何新类。它仅使用标准库代码,不需要特殊的编译器(moc)即可工作。那么,为什么信号和插槽比回调更受欢迎? C ++ 11是否简单地废弃了这些概念?

感谢。

3 个答案:

答案 0 :(得分:9)

两者之间存在巨大差异:线程。

传统的回调是在调用线程的上下文中始终调用。信号和插槽不是这样 - 只要线程正在运行一个事件循环(如果它是一个QThread),那么插槽就可以在任何线程中。

当然,您可以通过回调手动完成所有这些操作 - 多年来我使用Windows风格的消息泵编写了许多Win32应用程序,这些消息泵可以跨线程处理回调 - 但它很多样板代码,编写,维护或调试没什么好处。

答案 1 :(得分:3)

  

为什么信号和插槽比普通旧回调更好?

因为信号很像旧的回调,除了具有额外的功能和与Qt API深度集成之外。它不是火箭科学 - 回调+额外功能+深度整合比单独回调更大。 C ++可能最终提供了一种更简洁的回调方式,但这并不能取代Qt信号和插槽,更不用说它们已经过时了。

从Qt 5开始,插槽方面的相关性稍差,这使得信号可以连接到任何功能。但是,插槽仍然与Qt元系统集成,许多Qt API使用它来使事情有效。

是的,您可以使用回调来处理几乎所有信号应该实现的内容。但它并不容易,它更加冗长,它不能自动处理排队连接,它不会像信号那样与Qt集成,你也可以解决这个问题,但它会变得更加冗长。

在QML的情况下,现在Qt是Qt的主要焦点,你基本上坚持使用Qt的信号。所以我认为信号就在这里。

信号和插槽更好&#34;因为Qt是在概念上围绕它们构建的,所以它们是API的一部分,并被许多API使用。这些概念已经在Qt中存在了很长时间,从C ++开始提供很多回调支持的日子开始,除了从C继承的普通旧函数指针之外,这也是Qt不能简单地切换到std回调的原因 - 它会打破很多东西,是一种不必要的努力。同样的原因Qt继续使用那些邪恶的不安全的普通旧指针而不是智能指针。信号和插槽作为一个概念并不是过时的,在使用Qt时技术上更是如此。 C ++在游戏中简直太晚了。期望每个人现在都急于从他们自己的巨大代码库中实现,这是不现实的,因为C ++最终提供了替代方案作为语言标准库的一部分。

答案 2 :(得分:0)

通常:Signal和Slot与回调不同,因为它从处理程序(Slot)中将呼叫(信号)与信号(信号)分离。 这意味着:您可以将插槽注册到其他线程上,可以从多个插槽中监听一个信号,轻松更改排队策略。 但是它有它的价格(至少在QT世界中是这样的...):字符串评估,更多的内部控制流程等。 简而言之,它是更高层次的概念。

话虽如此,您可以通过简单的回调来完成所有操作,但这将彻底改变方向。