c++ 槽函數專題及常見問題 - CSDN

2021-01-07 CSDN技術社區

C++ Boost signal2信號/槽函數

根據網址:Boost C++ 庫 第 4 章 事件處理

http://zh.highscore.de/cpp/boost/eventhandling.html

signals2 基於Boost裡的另一個庫signals,實現了線程安全的觀察者模式。它是一種函數回調機制,當一個信號關聯了多個槽時,信號發出,這些槽將會被調用,當然,也可以僅僅關聯一個槽函數。

其實Qt也提供了它自己的信號和槽機制,那個是非常的靈活和好用的,但是它依賴於Qt的框架,所以退而求其次,選擇了Boost提供了signals2;

signals2庫位於命名空間boost::signals2中,為了使用它,需要包含頭文件<boost/signals2.hpp>;


信號(Signal)
signal是不可拷貝的,如果將signal作為類的成員變量,那麼類將不能被拷貝,除非使用只能智能或者是引用間接的持有它;

signal是一個模板類,它的定義如下:

template<typename Signature, typename Combiner = boost::signals2::optional_last_value<R>, typename Group = int, typename GroupCompare = std::less<Group>, typename SlotFunction = boost::function<Signature>, typename ExtendedSlotFunction = boost::function<R (const connection &, T1, T2, ..., TN)>, typename Mutex = boost::signals2::mutex> class signal;

第一個模板參數Signature的含義和function相同,也是一個函數類型,表示signal調用的函數(槽,事件處理handler),例如:

signal<void(int, double)> sig;
第二個模板參數Combiner是一個函數對象,它被稱為『合併器』,用於組合所有槽的返回值,默認是boost::signals2::optional_last_value<R>,返回最後一個被調用的槽的返回值;

第三個模板參數Group是槽編組的類型,你可以為你的槽設置不同的組,默認組的類型是int,通常情況下,不需要更改;

 

連接(connect)
connection connect(const group_type &group,const slot_type &slot, connect_position position = at_back)
它作為signal的成員函數,具有三個參數,第一個參數表示這個槽所屬的組,第二的參數表示信號觸發哪個槽函數,而最後的參數,表示槽函數在響應隊列中響應的位置,默認at_back表示這個槽函數出來隊列的末尾,它將在其他槽函數之後被調用。

實例
不帶返回值的槽函數

#include <iostream>#include <boost/signals2.hpp>using namespace boost::signals2;void slots1() {std::cout << "slot 1 called" << std::endl;}void slots2(int a) {std::cout << "slot 2 called " << a << std::endl;}void slots3(int a) {std::cout << "slot 3 called " << a << std::endl;}void slots4(int a) {std::cout << "slot 4 called " << a << std::endl;}int main() {signal<void()>sig1;sig1.connect(&slots1);sig1(); // the slot 1 calledsignal<void(int)>sig2;sig2.connect(1, &slots2);sig2.connect(2, &slots3);sig2.connect(2, &slots4, at_front); // slot 4 處於 第二組的最前面// 槽函數的調用,首先是比較連接的組的先後循序,然後根據組內循序調用;sig2(2); // slot 2 called slot 4 called slots3 calledreturn 0;}

當槽函數帶參數的時候,參數是通過信號傳遞的,所以需要保持信號和槽的參數的個數一致

結果如下:

 

 

 

帶參數的槽函數

#include <iostream>#include <boost/signals2.hpp>using namespace boost::signals2;int slots1(int a) {std::cout << "slot 1 called " << a << std::endl;return a + 1;}int slots2(int a) {std::cout << "slot 2 called " << a << std::endl;return a + 2;}int slots3(int a) {std::cout << "slot 3 called " << a << std::endl;return a + 3;}int main() {signal<int(int)> sig;sig.connect(&slots1);sig.connect(&slots2, at_front);sig.connect(&slots3);std::cout << *sig(0) << std::endl;return 0;}

在默認情況下,一個信號連接多個槽函數,並且槽函數是帶有返回值的,那麼這個信號將返回槽函數隊列中的最後一個的返回值。

結果如下:

 

 

 合併器

自定義合併器可以讓我們處理多個槽的返回值;

template<typename T>struct Combiner {typedef vector<T> result_type;template<typename InputIterator>result_type operator()(InputIterator first, InputIterator last) const {if(first == last) {return result_type(0);}return result_type(first, last);}};

這是一個典型的合併器,它返回一個擁有所有槽的返回值的一個vector,我們可以隨便定義合併器的返回類型,但要注意,一定要通過 typedef your_type result_type去註冊一下你的返回值類型;

具體的用法如下:

#include "boost/signals2.hpp"#include <iostream>#include <vector>using namespace std;using namespace boost::signals2;template<typename T>struct Combiner {typedef vector<T> result_type;template<typename InputIterator>result_type operator()(InputIterator first, InputIterator last) const {if(first == last) {return result_type(0);}return result_type(first, last);}};int slots3(int x) {return x + 3;}int slots4(int x) {return x + 4;}int main() {signal<int(int), Combiner<int> > sig;sig.connect(&slots3);sig.connect(&slots4);auto result = sig(1);for(const auto& i : result) {cout << i << endl;} return 0;}

 

斷開連接

// 以上省略一些代碼sig.connect(0, &slots1);sig.connect(0, &slots2);connection c1 = sig.connect(1, &slots3);sig.connect(2, &slots4);sig.connect(2, &slots5);sig();sig.disconnect(0); // 斷開組號為0的連接cout << sig.num_slots() << endl; // 還有三個連接sig();sig.disconnect(2); // 斷開組號為2的連接sig();c1.disconnect(); // 斷開slot3的連接

以上兩種方法都是可以的;

 

臨時連接
Boost提供了一個臨時的連接方式scoped_connection,也就是有作用域的連接;

// 以上省略了一些代碼sig.connect(&slots1);{ // 進入作用域, 建立臨時的連接scoped_connection sc = sig.connect(&slots2);cout << sig.num_slots() << endl;} // 離開作用域就自動斷開了連接cout << sig.num_slots() << endl;

阻塞連接

Boost提供了一個shared_connection_block實現阻塞和解除阻塞連接的操作,當它被析構(離開作用域)或者被顯式的調用unblock()就好解除阻塞;

// 以上省略一些代碼connection c1 = sig.connect(slots1);connection c2 = sig.connect(slots2);connection c3 = sig.connect(slots3);connection c4 = sig.connect(slots4);sig();{shared_connection_block block(c1); // 阻塞了c1sig(); //c1不會被調用}sig();

 

觸發成員中的槽函數

我們使用signal通常是為了實現類間的通信,實現觀察者模式;

我們需要使用bind()函數綁定槽函數,返回函數對象;

#include "boost/signals2.hpp"#include <iostream>#include <vector>using namespace std;using namespace boost::signals2;class C_Slots1 {public:int SL(int a) {cout << "slot 1 called" << a << endl;return a;}void SL1(int a, int b) {cout << "slot 2 called " << a << " " << b << endl;}};int main() {signal<int(int)> sig1;sig1.connect(bind(&C_Slots1::SL, &cs_1,_1)); // 綁定對象的成員signal<void(int, int)>sig2;sig2.connect(bind(&C_Slots1::SL1,&cs_1, _1, _2));cout << *sig1(10) << endl;sig2(1, 2);return 0;}

 

自動斷開
當槽函數被意外銷毀時,信號調用會發生未定義的行為。我們希望它能夠跟蹤槽函數的生命周期,當槽函數失效時,連接會自動斷開;

我們通過boost::shared_ptr來管理槽函數的生命周期,track()函數來跟蹤槽所使用的資源;(boost::shared_ptr與std::shared_ptr功能上一樣,但是實現不一樣,是不一樣的!!!)

#include "boost/signals2.hpp"#include <iostream>#include <vector>using namespace std;using namespace boost::signals2;class C_Slots {public:int SL(int a) const{cout << "slot 1 called" << a << endl;return a;}};int main() {typedef signal<int(int)> signal_t;signal_t sig;boost::shared_ptr<C_Slots> p_c1(new C_Slots2());sig5.connect(signal_t::slot_type(&C_Slots::SL, p_c1.get(), _1).track(p_c1));cout << *sig(2) << endl;return 0;}

 

槽函數是類的成員函數的時候

#include <iostream>#include <boost/signals2.hpp>#include <boost/bind.hpp>#include <boost/function.hpp> using namespace std;using namespace boost; template <typename signature>class Signal{ public: //typedef 信號 typedef boost::signals2::signal<signature> defSignal; typedef typename defSignal::slot_type defSlot; public: //連接槽函數 boost::signals2::connection connectFun(const defSlot& slot); //重載偽函數 void operator()(typename defSignal::template arg<0>::type a0,typename defSignal::template arg<1>::type a1); private: defSignal mSignal; }; //接收信號後響應的函數class FunRecv1{ public: void action(int a, int b){ cout << "add result" << a + b << endl; } }; //接收信號後響應的函數class FunRecv2{ public: void action(int a, int b){ cout << "multi result" << a * b << endl; } }; //實現template <typename signature>boost::signals2::connection Signal<signature>::connectFun(const defSlot& slot){ return mSignal.connect(slot);} template <typename signature>void Signal<signature>::operator()(typename defSignal::template arg<0>::type a0,typename defSignal::template arg<1>::type a1){ mSignal(a0,a1);}void main(){ Signal<void(int,int)> mysignal; FunRecv1 fun1; FunRecv2 fun2; //boost::function<void(int,int)> myfun = boost::bind(&FunRecv1::action,&fun1,_1,_2); //信號連接槽函數 boost::signals2::connection con1 = mysignal.connectFun(boost::bind(&FunRecv1::action,&fun1,100,200)); boost::signals2::connection con2 = mysignal.connectFun(boost::bind(&FunRecv2::action,&fun2,11,22)); mysignal(100,200); con2.disconnect(); mysignal(100,200); cin.get();}

相關焦點

  • c++信號與槽專題及常見問題 - CSDN
    開源庫下載:包含說明文檔,源碼,實例:https://download.csdn.net/download/u012372584/131624962、直接編譯會有錯誤,需要對源碼中的一句進行更改:將第419行 :typedef sender_set::const_iterator const_iterator; 更改為:typedef typename sender_set
  • 信號與槽專題及常見問題 - CSDN
    當一個信號被發射時,與其相關聯的槽將被執行,就象一個正常的函數調用一樣。信號-槽機制獨立於任何GUI事件循環。只有當所有的槽正確返回以後,發射函數(emit)才返回。如果存在多個槽與某個信號相關聯,那麼,當這個信號被發射時,這些槽將會一個接一個地被執行,但是它們執行的順序將會是不確定的,並且我們不能指定它們執行的順序。
  • java 信號與槽專題及常見問題 - CSDN
    QT信號/槽在我的理解中,QT和Android都是類似的開發框架,都是由開發團隊封裝了各式各樣的接口和數據結構.將一些問題的解決方法簡單化 比如QT中將線程封裝為QThread,派生類通過重寫run方法來將代碼投入到新的線程執行,而同樣的Android中的線程是Java自帶的Thread
  • redis 槽是什麼專題及常見問題 - CSDN
    如果在自己管理的槽編號範圍內,則把數據保存到數據槽中,然後返回執行結果 如果在自己管理的槽編號範圍外,則會把數據發送給正確的節點,由正確的節點來把數據保存在對應的槽中需要注意的是:Redis Cluster的節點之間會共享消息,每個節點都會知道是哪個節點負責哪個範圍內的數據槽虛擬槽分布方式中,由於每個節點管理一部分數據槽,數據保存到數據槽中。
  • c++中槽函數 - CSDN
    它是一種函數回調機制,當一個信號關聯了多個槽時,信號發出,這些槽將會被調用,當然,也可以僅僅關聯一個槽函數。sig;第二個模板參數Combiner是一個函數對象,它被稱為『合併器』,用於組合所有槽的返回值,默認是boost::signals2::optional_last_value<R>,返回最後一個被調用的槽的返回值;第三個模板參數Group是槽編組的類型,你可以為你的槽設置不同的組,默認組的類型是int,通常情況下,不需要更改;
  • slot vue 用法專題及常見問題 - CSDN
    如果有一個沒有名稱的槽,它就是默認槽。 就像之前一樣,如果我們想將內容添加到默認槽中,只需將其直接放在titled-frame組件中。但是,要將內容添加到命名槽中,我們需要用v-slot指令將代碼包裹在在template標記中。在v-slot之後添加冒號(:),然後寫出要傳遞內容的slot的名稱。注意,v-slot是Vue 2.6的新版本,所以如果你使用的是舊版本,則需要閱讀關於不推薦的slot語法的文檔。作用域插槽還需要知道的另一件事是插槽可以將數據/函數傳遞給他們的孩子。
  • c++ list slice專題及常見問題 - CSDN
    追加元素使用Go語言內置函數append()可以為切片動態地追加元素var slice []intslice = append(slice, 1)fmt.Printf("slice = %v\n", slice)//slice = [1]slice = append(slice, 2, 3)fmt.Printf("slice = %
  • C++ 的門門道道 | 技術頭條 - CSDN
    本文結合作者的工作經驗和學習心得,對C++語言的一些高級特性,做了簡單介紹;對一些常見的誤解,做了解釋澄清;對比較容易犯錯的地方,做了歸納總結;希望藉此能增進大家對C++語言了解,減少編程出錯,提升工作效率。一、我的程序裡用了全局變量,但為什麼進程正常停止的時候會莫名其妙的core掉?
  • c++中與操作時間開銷專題及常見問題 - CSDN
    缺點:使用有一定的難度,要更小心處理數據的一致性問題。所以,只要這個TA類對象裡沒有引用,或指針,那麼就不會產生問題。分析對象如何被複製到線程中入的:通過調用對象的拷貝構造函數實現的,具體看如下代碼。std::lock()函數模板作用:一次鎖住兩個或者兩個以上的互斥量(至少兩個);它不存在因為多個線程中鎖順序的問題,而引起死鎖風險。
  • C++、java 和 C 的區別
    2.java和c#裡面都有字符串型 和byte型, 但c++裡面沒有,但它是以另外的形式存儲這類型的數據的,比如 java和c#裡面的 byte其實就是unsigned char類型;c++中字符數組就能存儲字符串 (char a[]={"hello"}; ps:注意c++裡面定義數組 變量必須在中括號前面)。
  • [C++學習筆記]內存模型常見問題梳理
    C++內存模型常見問題梳理最近在學習C++,於是打算整理一些專題知識,梳理自己的學習成果。一來是回憶和整合,加深理解,再一個是希望大佬指點。目錄背景實習和學習需要。內存模型是一個經常會被問到的問題,加上語言本身比較複雜,經常日常使用往往不會在意,所以特此探究一下。正文編譯過程C++的程序從原始碼開始,如何變成一個可執行的程序呢?
  • cluster redis 分槽專題及常見問題 - CSDN
    為了使得集群能夠水平擴展,首要解決的問題就是如何將整個數據集按照一定的規則分配到多個節點上,常用的數據分片的方法有:範圍分片,哈希分片,一致性哈希算法和虛擬哈希槽等。範圍分片假設數據集是有序,將順序相臨近的數據放在一起,可以很好的支持遍歷操作。範圍分片的缺點是面對順序寫時,會存在熱點。
  • github覆蓋本地專題及常見問題 - CSDN
    參考文獻[1] Github進行fork後如何與原倉庫同步 https://blog.csdn.net/matrix_google/article/details/80676034[2] git分支查看及切換 https://blog.csdn.net/qq_26710805/article/details/80674006[3] git 放棄本地修改
  • 面試:C/C++常見庫函數實現
    作者:wxquare連結:https://www.cnblogs.com/wxquare/p/5014445.html1、void *mymemcpy(void *dest, const void* src, size_t n);內存拷貝函數
  • c++之重載函數學習總結
    +編譯器裡面編譯時沒有問題的,如果放在c語言編譯器裡面編譯是會報錯的:root@txp-virtual-machine:/home/txp# gcc test5.ctest5.c:8:5: error: conflicting types for 『func』 int func(int a, int b)
  • C++ 優先隊列priority_queue
    +/datastruct$ g++ priorityqueue.cpp -o commonsort -std=c++11albert@home-pc:/mnt/c++/datastruct$ ./commonsort16151310985321如果是完整排序使用優先隊列就有些麻煩了,還不如直接調用 std::sort 函數,但是如果只取部分數據的話,優先隊列還是非常方便快速的,比如下面這個問題。
  • 新手入門:關於C++中的內聯函數(inline)
    >   在c++中,為了解決一些頻繁調用的小函數大量消耗棧空間或者是叫棧內存的問題,特別的引入了inline修飾符,表示為內聯函數。  可能說到這裡,很多人還不明白什麼是棧空間,其實棧空間就是指放置程序的局部數據也就是函數內數據的內存空間,在系統下,棧空間是有限的,如果頻繁大量的使用就會造成因棧空間不足所造成的程序出錯的問題,函數的死循環遞歸調用的最終結果就是導致棧內存空間枯竭。
  • C++ | 虛函數簡介
    本文將簡單探究一下 c++ 中的虛函數實現機制。本文主要基於 vs2013 生成的 32 位代碼進行研究,相信其它編譯器(比如,gcc)的實現大同小異。先從對象大小開始 假設我們有如下代碼,假設 int 佔 4 字節,指針佔 4 字節。
  • C/C++可變參數函數
    c/c++支持可變參數的函數,即函數的參數是不確定的。一、為什麼要使用可變參數的函數?一般我們編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的所有實際參數。但在某些情況下希望函數的參數個數可以根據需要確定,因此c語言引入可變參數函數。這也是c功能強大的一個方面,其它某些語言,比如fortran就沒有這個功能。典型的可變參數函數的例子有大家熟悉的printf()、scanf()等。二、c/c++如何實現可變參數的函數?
  • Android Multimedia框架總結(六)C++中MediaPlayer的C/S架構
    /52435789前面幾節中,都是通過java層調用到jni中,jni向下到c++層並未介紹 看下Java層一個方法在c++層 MediaPlayer後續過程 frameworks/av/media/libmedia/MediaPlayer.cpp 找一個我們之前熟悉的setDataResource方法看下C/S模式的過程,亦可參考Android Multimedia