日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++?Boost.Signals2信號/槽概念_C 語言

作者:無水先生 ? 更新時間: 2023-01-01 編程語言

一、關于Boost.Signals2

Boost.Signals2 實現了信號/槽的概念。一個或多個函數(稱為槽)與可以發出信號的對象相關聯。每次發出信號時,都會調用鏈接的函數。

信號/槽概念在開發具有圖形用戶界面的應用程序時非常有用。可以對按鈕進行建模,以便在用戶單擊它們時發出信號。它們可以支持指向許多函數的鏈接以處理用戶輸入。這樣就可以靈活地處理事件。

std::function 也可用于事件處理。 std::function 和 Boost.Signals2 之間的一個重要區別是 Boost.Signals2 可以將多個事件處理程序與單個事件相關聯。因此,Boost.Signals2更適合支持事件驅動開發,應該是任何需要處理事件的首選。

Boost.Signals2 繼承了庫 Boost.Signals,后者已被棄用且本書未討論。

Table of Contents

Signals

Connections

Multithreading

二、關于Signals庫

Boost.Signals2 提供類 boost::signals2::signal,可用于創建信號。此類在 boost/signals2/signal.hpp 中定義。或者,您可以使用頭文件 boost/signals2.hpp,這是一個主頭文件,定義了 Boost.Signals2 中可用的所有類和函數。

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

示例 67.1。 “你好世界!”使用 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() 的函數才能與信號相關聯。

lambda 函數通過 connect() 與信號 s 相關聯。因為 lambda 函數符合所需的簽名 void(),所以關聯已成功建立。每當觸發信號 s 時,都會調用 lambda 函數。

信號是通過像調用常規函數一樣調用 s 來觸發的。此函數的簽名與作為模板參數傳遞的簽名匹配。括號是空的,因為 void() 不需要任何參數。調用 s 會產生一個觸發器,而該觸發器又會執行之前與 connect() 相關聯的 lambda 函數。

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

示例 67.2。 “你好世界!”使用 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();
}

Example?67.5?

示例 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 中,兩個 lambda 函數與信號關聯。第一個 lambda 函數返回 1,第二個返回 2。

示例 67.7 將 2 寫入標準輸出。 s 正確接受了兩個返回值,但除了最后一個之外的所有返回值都被忽略了。默認情況下,只返回所有關聯函數的最后一個返回值。

請注意,s() 不會直接返回上次調用的函數的結果。返回類型為 boost::optional 的對象,取消引用時返回數字 2。觸發與任何函數無關的信號不會產生任何返回值。因此,在這種情況下,boost::optional 允許 Boost.Signals2 返回一個空對象。 boost::optional 在第 21 章中介紹。

可以自定義信號,以便相應地處理各個返回值。為此,必須將組合器作為第二個模板參數傳遞給 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::signal。 boost::signals2::signal 期望組合器定義一個名為 result_type 的類型,它表示 operator() 返回值的類型。由于這個類型沒有被標準算法定義,所以編譯器會報錯。

請注意,不可能將迭代器 first 和 last 直接傳遞給 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';
}

練習

創建一個帶有課程按鈕的程序。該類應代表圖形用戶界面中的按鈕。添加成員函數 add_handler() 和 remove_handler() ,它們都希望傳遞一個函數。如果調用另一個名為 click() 的成員函數,則應依次調用已注冊的處理程序。通過注冊一個將消息寫入標準輸出的處理程序來實例化按鈕并測試該類。調用 click() 來模擬鼠標點擊按鈕。

原文鏈接:https://yamagota.blog.csdn.net/article/details/128166475

欄目分類
最近更新