1. 程式人生 > >QT中的訊號-槽比我們常用的callback到底牛在哪裡?

QT中的訊號-槽比我們常用的callback到底牛在哪裡?

這裡寫圖片描述

剛剛接觸qt, 發現有一個東西叫signal slot ,翻譯為訊號槽。

免責宣告,我是一個beginner。Google了很多資料,很多文章寫signal slot的。所以,這裡只是把我個人覺得精彩的地方整理在一起,再偶爾加上一點點自己的看法而已。

看問題,再stackoverflow上看到這樣一個提問,大意思是這樣:
他們團隊的qt專案中,一位資深的軟體工程師運用了大量的c-style的回撥函式,而沒用使用qt中很精妙的signal slot機制。

回撥有哪些缺點?
Callbacks have two fundamental flaws: Firstly, they are not type-safe. We can never be certain that the processing function will call the callback with the correct arguments. Secondly, the callback is strongly coupled to the processing function since the processing function must know which callback to call.

什麼叫型別安全?
型別安全很大程度上可以等價於記憶體安全,型別安全的程式碼不會試圖訪問自己沒被授權的記憶體區域。“型別安全”常被用來形容程式語言,其根據在於該門程式語言是否提供保障型別安全的機制;有的時候也用“型別安全”形容某個程式,判別的標準在於該程式是否隱含型別錯誤。型別安全的程式語言與型別安全的程式之間,沒有必然聯絡。好的程式設計師可以使用型別不那麼安全的語言寫出型別相當安全的程式,相反的,差一點兒的程式設計師可能使用型別相當安全的語言寫出型別不太安全的程式。絕對型別安全的程式語言暫時還沒有。

什麼是訊號、槽?
A signal is an observable event, or at least notification that the
event happened.
A slot is a potential observer, typically in the form a function to be
called.
You connect a signal to a slot to establish the observableobserver
relationship.

訊號槽有啥缺點呢?
Compared to callbacks, signals and slots are slightly slower because of the increased flexibility they provide, although the difference for real applications is insignificant.
In general, emitting a signal that is connected to some slots, is approximately ten times slower than calling the receivers directly, with non-virtual function calls.
This is the overhead required to locate the connection object, to safely iterate over all connections (i.e. checking that subsequent receivers have not been destroyed during the emission), and to marshall any parameters in a generic fashion. While ten non-virtual function calls may sound like a lot, it’s much less overhead than any new or delete operation, for example. As soon as you perform a string, vector or list operation that behind the scene requires new or delete, the signals and slots overhead is only responsible for a very small proportion of the complete function call costs. The same is true whenever you do a system call in a slot; or indirectly call more than ten functions. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won’t even notice.

官方出品(節選)

In Qt, we have an alternative to the callback technique: We use signals and slots. A signal is emitted when a particular event occurs. Qt’s widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a function that is called in response to a particular signal. Qt’s widgets have many pre-defined slots, but it is common practice to subclass widgets and add your own slots so that you can handle the signals that you are interested in.

簡單的例子:

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