1. 程式人生 > >【C++】boost::bind和函式物件一起使用實現便捷的非同步程式設計

【C++】boost::bind和函式物件一起使用實現便捷的非同步程式設計

     在C++面向物件程式設計中,觀察者模式是大家熟知的實現非同步程式設計的一種模式。

     觀察者模式定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動更新。如下圖所示:

     觀察者模式提供了一種物件設計,讓主題和觀察者之間鬆耦合:

  •       執行時我們用新的觀察者取代現有的觀察者,主題不會受到影響。
  •       有新型別的觀察者出現時,主題程式碼不需要更改。
  •       可以輕易地獨立使用或複用主題或者觀察者。
  •       改變主題或者觀察者的任何一方都不會影響另一方。

UML 關係圖:

       傳統的這種觀察者模式雖然是鬆耦合的,但是observer和subject之間還是互相關聯的關係。實現observer的程式碼檔案,必須包含subject的定義標頭檔案;實現subject的程式碼檔案,必須包含observer的定義標頭檔案。由於彼此有稀鬆的互相“包含”關係, 使得觀察者模式的使用得到了很大限制。有的情況下,編譯都難通過,浪費很多時間。這個問題是是典型的“兩個類相互包含的問題”。

問題描述:

        A類包含B類的例項,而B類也包含A類的例項

(你可以將A當成subject,而B當成observer)

錯誤的解法

 A檔案包含B,而B檔案又包含A檔案,這樣就形成死迴圈 (在本問你中,實現observer的程式碼檔案,必須包含subject的定義標頭檔案;實現subject的程式碼檔案,必須包含observer的定義標頭檔案。subject和observer彼此有稀鬆的互相“包含”關係, 這使得觀察者模式的使用得到了很大限制。)

#include "B.h"  

class A  

{  

    int i;  

    B b;  

};  

#include "A.h"  

class B  

{  

    int i;  

    A   a;  

};  

常規的解決辦法以及注意事項 

1)主函式只需要包含b.h 就可以,因為b.h中包含了a.h

     2a.h中不需要包含b.h,但要宣告class b在避免死迴圈的同時也成功引用了b

     3)包含class  b 而沒有包含標頭檔案 "b.h",這樣只能宣告  b型別的指標!!!!而不能例項化!!!!

 有沒有更好的辦法呢?當然有。本文就是要解決這個問題。

從理論上將,subject物件是不需要過度關注observer物件的,也就是subject不需要去關聯observer型別。只需要兩步:

    (1)observer物件向subject物件發起註冊,可以只傳遞一個函式指標。

    (2)當subject有事情需要通知時,可以呼叫上步的函式。

這種方式下,observer 和subject之間的耦合性被進一步降低。subject不再刻意關注observer物件,其組成也不再關聯一個observer物件指標或一個observer物件指標連結串列。

我們用boost::bind這個神兵利器來解決這個問題。

     第一步,在observer物件中初始化的時候定義一個boost::function函式物件,如 boost::function<void(NotifyParams)> State_Update_Func;

     第二步,將該boost::function物件繫結到本observer物件對接收到subject通知後的處理函式:

      State_Update_Func =boost::bind(&Observer::Notification_Process_Func,this,_1);

   第三步,進行註冊:


     m_Subject.Subscribe (   State_Update_Func);

第四步,實現subject的註冊函式:

inline void  Subscribe( boost::function<void(NotifyParams)> Listener) { m_Status_Update_Func=Listener;};

第四步,實現subject的通知函式:

  void  CSubject::NotifySubscriber()
  {
      if ( m_Status_Update_Func )
           m_Status_Update_Func(m_NotifyParams);

  }

這樣,當subject呼叫NotifySubscriber時,observer的處理函式就會被自動呼叫,實現了非同步程式設計。

一個問題討論:

(1)有人認為,通過boost::bind繫結函式物件時,如果被繫結的函式為成員函式,成員函式的引數最好都是傳值,而不要傳遞引用或指標。

例如,假設在多執行緒環境下,我們要根據IP非同步的查詢一個裝置

mp_context->executeInThread(boost::bind(&CMbxdHandler::queryDeviceAsync, this,strIP);

查詢函式最好引數為const string ipaddress,而不是const string &ipaddress

 void CQueryHandler::queryDeviceAsync(const string ipaddress)

這樣的理由在於,在多執行緒呼叫環境下,當CQueryHandler被析構時,有可能strIP已經被析構,此時再去使用它的引用,將導致core dump.

其實是多慮了,boost::bind已經幫你考慮了這個問題。boost::bind會自動將每個傳入的引數物件拷貝一份,只有當你顯示將引數設定為boost:ref時,引數才可能是引用傳遞。下面寫一段程式碼來驗證這個問題:

#include "boost/ref.hpp"
#include "boost/function.hpp"
#include "boost/bind.hpp"

#include <string>
#include <iostream>

using namespace std;

class A
{
public:
	A(const string& s)
	{
		_name = s;
	}
	A(const A& a)
	{
		_name = a._name;
		cout << "copy function" << "\n";
	}
	void set(const string& s)
	{
		_name = s;
	}
	string get() const
	{
		return _name;
	}
private:
	string _name;
};

void show_me(const A& a)
{
	cout << "show me: " << a.get() << "\n";
}

int main()
{
	A* p_a = new A("Jack");
	cout << "before bind" << "\n";
	boost::function<void ()> f = boost::bind(&show_me, *p_a);
	cout << "after bind" << "\n";
	p_a->set("Tom");
	delete p_a;
	f();
	f();
	f();
}

可以看到,執行結果在bind之前和之後都是內部拷貝的 “Jack”。而不是通過傳遞引用,打印出“tom”