您的位置:首页 > 编程语言 > C语言/C++

【C++】boost::bind和函数对象一起使用实现便捷的异步编程

2014-05-04 16:12 567 查看
在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;

};

l 常规的解决办法以及注意事项

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

2)a.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”
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: