Signals-The Boost C++ Libraries

雪域迷影發表於2020-11-08

本文翻譯自Signals

Signals

Boost.Signals2提供了boost::signals2::signal類,可用於建立訊號。 此類在boost/signals2/signal.hpp中定義。 或者,您可以使用標頭檔案boost/signals2.hpp,它是一個主標頭檔案,定義了Boost.Signals2中可用的所有類和函式。

Boost.Signals2定義boost::signals2::signal和其他類,以及名稱空間boost::signals2中的所有函式。

示例67.1 “Hello,World” 使用boost::signals2::signal

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello, world!\n"; });
  s();
}

boost::signals2::signal是一個類别範本,它期望將用作事件處理程式的函式的簽名作為模板引數。 在例67.1中,只有具有void()簽名的函式才能與訊號s關聯。

Lambda函式通過connect()與訊號s關聯。 因為lambda函式符合所需的簽名void(),所以成功建立了關聯。 每當訊號s觸發時,都會呼叫lambda函式。

像常規函式一樣通過呼叫來觸發訊號。 該函式的簽名與作為模板引數傳遞的簽名相匹配。 方括號為空,因為void()不需要任何引數。 呼叫s會導致觸發器,該觸發器又執行先前與connect()關聯的lambda函式。

例67.1也可以用std::function實現,如例67.2所示。

示例67.2 “Hello,World!” 使用std::function

#include <functional>
#include <iostream>

int main()
{
  std::function<void()> f;
  f = []{ std::cout << "Hello, world!\n"; };
  f();
}

示例67.2中,當呼叫f時,也會執行lambda函式。 雖然std::function僅可用於示例67.2之類的場景,但Boost.Signals2提供了更多種類。 例如,它可以將多個功能與特定訊號關聯(請參見示例67.3)。

示例67.3 使用boost::signals2::signal的多個事件處理程式

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello"; });
  s.connect([]{ std::cout << ", world!\n"; });
  s();
}

boost::signals2::signal允許您通過重複呼叫connect()將多個功能分配給特定訊號。 無論何時觸發訊號,函式都會按照它們與connect()關聯的順序執行。

還可以在connect()的過載版本的幫助下顯式定義該順序,該版本期望將int型別的值作為附加引數(示例67.4)。

示例67.4 具有明確順序的事件處理程式

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect(1, []{ std::cout << ", world!\n"; });
  s.connect(0, []{ std::cout << "Hello"; });
  s();
}

與前面的示例一樣,示例67.4顯示Hello,world!

要從訊號中釋放關聯的函式,請呼叫disconnect()

示例67.5 斷開事件處理程式與boost::signals2::signal的連線

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

void hello() { std::cout << "Hello"; }
void world() { std::cout << ", world!\n"; }

int main()
{
  signal<void()> s;
  s.connect(hello);
  s.connect(world);
  s.disconnect(world);
  s();
}

示例67.5僅輸出Hello,因為在觸發訊號之前已釋放了與world()的關聯。

除了connect()disconnect()外,boost:: signals2::signal還提供了一些其他成員函式(請參見示例67.6)。

示例67.6 boost::signals2::signal的其他成員函式

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  s.connect([]{ std::cout << "Hello"; });
  s.connect([]{ std::cout << ", world!"; });
  std::cout << s.num_slots() << '\n';
  if (!s.empty())
    s();
  s.disconnect_all_slots();
}

num_slots()返回關聯函式的數量。 如果沒有函式關聯,則num_slots()返回0。empty()告訴您是否連線了事件處理程式。 disconnect_all_slots()的功能恰如其名:釋放所有現有的關聯。

示例67.7 處理事件處理程式的返回值

#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<int()> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::cout << *s() << '\n';
}

示例67.7中,兩個λ函式與訊號s相關聯。第一個lambda函式返回1,第二個返回2。

例67.72寫入標準輸出。 s正確接受了兩個返回值,但忽略了最後一個返回值。預設情況下,僅返回所有關聯函式的最後一個返回值。

請注意,s()不會直接返回最後一個呼叫函式的結果。返回型別為boost::optional的物件,取消引用後將返回數字2。觸發與任何功能均不相關的訊號不會產生任何返回值。因此,在這種情況下,boost::optional允許Boost.Signals2返回一個空物件。第21章介紹了boost::optional

可以自定義訊號,以便相應地處理各個返回值。為此,必須將組合器傳遞給boost::signals2::signal作為第二個模板引數。

示例67.8 用使用者定義的組合器找到最小的返回值

#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::signals2;

template <typename T>
struct min_element
{
  typedef T result_type;

  template <typename InputIterator>
  T operator()(InputIterator first, InputIterator last) const
  {
    std::vector<T> v(first, last);
    return *std::min_element(v.begin(), v.end());
  }
};

int main()
{
  signal<int(), min_element<int>> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::cout << s() << '\n';
}

組合器是帶有過載operator()的類。該操作符會被兩個迭代器自動呼叫,這兩個迭代器用於訪問與特定訊號關聯的功能。當取消迭代器的引用時,將呼叫函式,並且它們的返回值在組合器中變得可用。然後可以使用標準庫中的通用演算法(例如std::min_element())來計算並返回最小值(請參見示例67.8)。

boost::signals2::signal使用boost::signals2::optional_last_value作為預設組合器。該組合器返回型別為boost::optional的物件。使用者可以使用任何型別的返回值定義組合器。例如,示例67.8中的組合器min_element將作為模板引數傳遞的型別返回給min_element

無法將諸如std::min_element()之類的演算法作為模板引數直接傳遞給boost::signals2::signalboost::signals2::signal期望組合器定義一個稱為result_type的型別,該型別表示operato()返回的值的型別。由於標準演算法未定義此型別,因此編譯器將報告錯誤。

請注意,不可能首先將迭代器直接傳遞到std::min_element(),因為此演算法需要正向迭代器,而組合器則與輸入迭代器一起使用。這就是為什麼在使用std::min_element()確定最小值之前,使用向量儲存所有返回值的原因。

例67.9修改了組合器,以將所有返回值儲存在容器中,而不是對其求值。它將所有返回值儲存在一個向量中,然後由s()返回。

示例67.9使用使用者定義的合併器接收所有返回值

#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::signals2;

template <typename T>
struct return_all
{
  typedef T result_type;

  template <typename InputIterator>
  T operator()(InputIterator first, InputIterator last) const
  {
    return T(first, last);
  }
};

int main()
{
  signal<int(), return_all<std::vector<int>>> s;
  s.connect([]{ return 1; });
  s.connect([]{ return 2; });
  std::vector<int> v = s();
  std::cout << *std::min_element(v.begin(), v.end()) << '\n';
}

練習

建立帶有類button的一個程式。 該類應表示圖形使用者介面中的按鈕。 新增成員函式·add_handler()remove_handler()都希望傳遞一個函式。 如果呼叫了另一個稱為click()的成員函式,則應依次呼叫已註冊的處理程式。 例項化按鈕並通過註冊將訊息寫入標準輸出的處理程式來測試類。 呼叫click()`以模擬滑鼠在按鈕上的單擊。

Prev Next

相關文章