Boost::signals2 類QT的信號槽實現機制

signals2 基於Boost裏的另一個庫signals,實現了線程安全的觀察者模式。它是一種函數回調機制,當一個信號關聯了多個槽時,信號發出,這些槽將會被調用。google的base庫裏用的多的模式是:observer,delegate,callback。用來做相互調用的解耦,但是感覺也沒有信號槽用的方便。

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

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

文章目錄

信號(Signal)

連接(connect)

實例

不帶返回值的槽函數

合併器

斷開連接

臨時連接

阻塞連接

觸發成員中的槽函數

自動斷開


信號(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 called
    signal<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 called
    return 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); // 阻塞了c1
    sig(); //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;
}

 

 boost---signals2使用詳解

1、signals2實現了線程安全的“觀察者模式”,也稱作:信號---插槽,他是一種函數的回調機制,當信號發出時,相應的槽函數會被調用,有點類似於QT中的信號槽。

2、特點:

(1)、一個信號可以與多個插槽函數綁定;

(2)、一個信號與多個插槽函數綁定時,插槽函數可以設置自己被調用的順序:boost::signals2::at_front、boost::signals2::at_back;

(3)、一個信號與多個插槽函數綁定時,可以對插槽函數進行分組:組號小的函數先調用,組號大的後調用;同一組號中,調用順序根據boost::signals2::at_back等設置的順序調用;

(4)、信號與插槽函數一旦斷開連接,就不能再次被綁定;

(5)、當前信號綁定的插槽函數數目:num_slots() ,信號是否綁定了插槽函數:empty();

(6)、我們可以通過合併器獲取插槽函數被調用後的返回值;

(7)、以下類可以幫助我們進行更加靈活的信號連接管理,boost::signals2::connection:斷開連接、判斷是否連接;boost::signals2::shared_connection_block:可以阻塞連接、解除阻塞連接、判斷當前連接是否阻塞;boost::signals2::scoped_connection:對象析構時會自動釋放連接;
 

// Single_test.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//
 
#include <iostream>
#include <vector>
#include <boost/signals2.hpp>
using namespace std;
 
void func1()
{
	std::cout << "func1()函數被調用." << std::endl;
}
 
void func2()
{
	std::cout << "func2()函數被調用." << std::endl;
}
 
void func3()
{
	std::cout << "func3()函數被調用." << std::endl;
}
 
class my_object
{
public:
	void operator()(int param)
	{
		std::cout << "仿函數被調用: " << param << std::endl;
	}
};
 
//測試組號時用
template<int N>
struct Slot
{
	void operator()()
	{
		std::cout << "Slot current N value is : " << N << std::endl;
	}
};
 
int func4(int param)
{
	std::cout << "func3()函數被調用." << std::endl;
	return param + 10;
}
 
//合併器
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 func5(int param)
{
	std::cout << "func5()函數被調用." << std::endl;
	return param + 5;
}
 
int func6(int param)
{
	std::cout << "func6()函數被調用." << std::endl;
	return param + 6;
}
 
int func7(int param)
{
	std::cout << "func7()函數被調用." << std::endl;
	return param + 7;
}
 
int func8(int param)
{
	std::cout << "func8()函數被調用." << std::endl;
	return param + 8;
}
 
int main()
{
	//設置槽的調用順序
	boost::signals2::signal<void()> sig_void;
	sig_void.connect(&func1);  
	sig_void.connect(&func2, boost::signals2::at_front);//將會第一個被調用
	sig_void();
	
	//設置槽帶參數
	boost::signals2::signal<void(int)> sig_obj;
	sig_obj.connect(my_object());
	sig_obj(10);
 
	//設置組號:根據組號分成組操作,組號小的函數先調用,組號大的後調用;
	//同一組號,調用順序根據boost::signals2::at_back等設置的順序調用
	boost::signals2::signal<void()> sig_slot;
	sig_slot.connect(10, Slot<1>());
	sig_slot.connect(10, Slot<2>());
	sig_slot.connect(9, Slot<3>());
	sig_slot.connect(9, Slot<4>(), boost::signals2::at_back);
	sig_slot.connect(11, Slot<5>());
	sig_slot.connect(11, Slot<6>());
	sig_slot.connect(11, Slot<7>(), boost::signals2::at_front);
 
	std::cout << "sig_slot信號關聯的插槽數量: " << sig_slot.num_slots() << std::endl;
	
	if(!sig_slot.empty())
		sig_slot();
 
	//接收槽函數的返回值
	boost::signals2::signal<int(int)> sig_return;
	sig_return.connect(&func4);
 
	//一個信號對應一個槽函數時可以
	int m = *sig_return(100);
	std::cout << m << std::endl;
	//斷開之前所有的信號-槽連接
	sig_return.disconnect_all_slots();
 
	//一個信號對應多個槽函數時:使用合併器獲取所有的返回值
	boost::signals2::signal<int(int), Combiner<int> > sig_ret_combin;
	sig_ret_combin.connect(&func5);
	sig_ret_combin.connect(&func6);
	
	auto result = sig_ret_combin(200);
	for (const auto& i : result)
	{
		std::cout << i << std::endl;
	}
 
	//通過boost::signals::shared_connection_block 管理連接
	boost::signals2::signal<int(int)> sig_mgr;
	boost::signals2::shared_connection_block c = sig_mgr.connect(&func7);
 
	c.block();//連接被阻塞
	if (c.blocking())
		std::cout << "連接被阻塞" << std::endl;
	sig_mgr(12);//不會被調用
	c.unblock();//解除阻塞
	sig_mgr(12);//會被調用
	
	//boost::signals::connection管理連接
	boost::signals2::signal<int(int)> sig_mgr1;
	boost::signals2::connection c1 = sig_mgr1.connect(&func7);
	if (c1.connected())
		sig_mgr1(12);
	//斷開連接
	c1.disconnect();
 
	//boost::signals::scoped_connection管理:析構時會自動釋放連接
	boost::signals2::signal<int(int)> sig_mgr2;
	{
		boost::signals2::scoped_connection c2 = sig_mgr2.connect(&func8);
		std::cout << "當前fun8的連接數: " << sig_mgr2.num_slots() << std::endl;
	}
	//連接已被釋放,相應槽函數不會被調用
	sig_mgr2(12);
 
	return 1;
}
 
 

轉載自:https://blog.csdn.net/qq_34347375/article/details/86620845

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章