c++自定義消息管理機制升級版(std::bind && std::function)

      自從上次寫完借(chao)鑑(xi)cocos2dx的消息管理機制之後,也在實際中試着用了幾次,只能說對於繼承Ref基類方面,一直是最大的不足之處,而後更是遇到了一個問題,使我不得不拋棄這個方式,轉而尋找別的方法。

      問題(bug):在多重繼承中,對於子類如果是先繼承別的類,在訂閱消息強轉成Ref類型指針時,由於c++對象的結構,強轉過程中會發生地址偏移,同時在調用時對象每個成員偏移,最後導致在接受消息的函數中對成員變量操作時無效。起初我以爲是對象強轉用的是static_cast的原因,試着換成dynamic_cast,然並卵。後經同事提醒知道是這個原因,隨後去用cocos2dx的__NotificationCenter測試,發現也是存在這個問題,沒有解決,哈哈,也不知道這算不算引擎bug。

      解決方案:c++中有個function和bind模板,將函數和對象打包成一個函數,可以在類外調用,類似於c#中的delegate,但更強大,通過這個方法完美解決,同時順道解決了繼承問題,不再需要繼承Ref的形式了。



1.NotificationObserver觀察者對象類

      與上一篇相同,存儲訂閱消息的對象,以及消息名字,函數指針等一系列的數據信息。NotificationObserver類寫在NotificationCenter.h中,並且所有函數體也都直接實現了。


//觀察者類,負責管理通知訂閱消息的類
class NotificationObserver 
{
public:
	NotificationObserver(std::function<void(int)>	_func, string _Messagename)
	{
		m_Messagename = _Messagename;
		m_func = _func;
	}

	~NotificationObserver()
	{
	}
	string GetName()
	{
		return m_Messagename;
	}
	void ObserverCallBack(int ref)
	{
		if (m_func)
		{
			m_func(ref);
		}
	}
	function<void(int)> GetFunction()
	{
		return m_func;
	}
	//重載運算符
	bool operator ==(NotificationObserver*	_observe)const
	{
		return m_Messagename == _observe->m_Messagename;
	}
private:
	string	m_Messagename;//消息名字 唯一標籤,用以發送消息時消息管理器根據該標籤來判定調用哪個回調函數
	std::function<void(int)>	m_func;//c++ function模板,存儲回調函數
};


這裏單拿出來其實只是湊篇幅,分條理。


2.NotificationCenter消息管理中心

消息中心必須是單例,在一個程序中只能有一個存在,只是將原本的方式改成了function模板。

NotificationCenter.h

#ifndef __NOTIFICATION_CENTER_
#define __NOTIFICATION_CENTER_

#pragma once
//#include"Ref.h"
#include <iostream>
#include<vector>
#include <list>
#include<functional>//這個是function和bind模板的庫
using namespace std;
using namespace std::placeholders;//這個是bind模板中動態參數的命名空間

//默認函數帶一個int型的參數
class NotificationObserver;
class NotificationCenter
{
public:
	NotificationCenter();
	~NotificationCenter();
	static NotificationCenter*getInstance();
	static void destroyInstance();
	//添加觀察者
	//訂閱消息的函數,名字標籤
	void addObserver(std::function<void(int)>	_func, string _Messagename);
	//移除觀察者
	void removeObserver(string _Messagename);
	//清空觀察者
	void removeAllObservers();
	//判斷該觀察者是否已經添加過了
	bool ObserverExisted(function<void(int)>	_func, string _Messagename);
	//發送消息
	void PostNotification(string _Messagename);
	//帶參數的發送消息
	void PostNotification(string _Messagename, int _ref);
private:
	vector<NotificationObserver*>	m_array;
};
//觀察者類,負責管理通知訂閱消息的類
class NotificationObserver 
{
public:
	NotificationObserver(std::function<void(int)>	_func, string _Messagename)
	{
		m_Messagename = _Messagename;
		m_func = _func;
	}

	~NotificationObserver()
	{
	}
	string GetName()
	{
		return m_Messagename;
	}
	void ObserverCallBack(int ref)
	{
		if (m_func)
		{
			m_func(ref);
		}
	}
	function<void(int)> GetFunction()
	{
		return m_func;
	}
	//重載函數運算符
	bool operator ==(NotificationObserver*	_observe)const
	{
		return m_Messagename == _observe->m_Messagename;
	}
private:
	string	m_Messagename;
	std::function<void(int)>	m_func;
};
#endif

NotificationCenter.cpp
#include "NotificationCenter.h"
static NotificationCenter *_notification = nullptr;
NotificationCenter::NotificationCenter()
{
	m_array.clear();
}
NotificationCenter::~NotificationCenter()
{
}
NotificationCenter*NotificationCenter::getInstance()
{
	if (_notification != nullptr)
	{
		return _notification;
	}
	_notification = new NotificationCenter;
	return _notification;
}

void NotificationCenter::destroyInstance()
{       if(_notification!=nullptr)
	    delete _notification;
}

bool NotificationCenter::ObserverExisted(std::function<void(int)> _func, string _Messagename)
{
	NotificationObserver *obj = nullptr;
	NotificationObserver* _observer=new NotificationObserver(_func,_Messagename);
	bool _existed = false;
	for each (obj in m_array)
	{
		if (!obj)
		{
			continue;
		}
		
		if (obj==_observer)
		{
			_existed=true;
			break;
		}
	}
	delete _observer;
	return _existed;
}
void NotificationCenter::addObserver(std::function<void(int)> _func, string _Messagename)
{
	if (this->ObserverExisted(_func, _Messagename))
	{
		return;
	}
	NotificationObserver *observe = new NotificationObserver(_func, _Messagename);
	m_array.push_back(observe);
}

void NotificationCenter::removeObserver(string _Messagename)
{
	//vector<NotificationObserver*>::iterator itor;
	auto itor = m_array.begin();
	int i = 0;
	for (itor = m_array.begin(); itor != m_array.end();)
	{
		if (((*itor)->GetName() == _Messagename))
		{
			//delete m_array.at(i);
			delete *itor;
			itor = m_array.erase(itor);
		}
		else{
			i++;
			itor++;
		}
	}
}
void NotificationCenter::removeAllObservers()
{
	vector<NotificationObserver*>::iterator itor;
	itor = m_array.begin();
	for (itor = m_array.begin(); itor != m_array.end();)
	{
		delete *itor;
		itor++;
	}
	m_array.clear();
}

//發送消息
void NotificationCenter::PostNotification(string _Messagename)
{
	int _ref = 0;
	PostNotification(_Messagename, _ref);
}
void NotificationCenter::PostNotification(string _Messagename, int _ref)
{
	for (auto sp : m_array)
	{
		if (sp->GetName() == _Messagename)
		{
			sp->ObserverCallBack(_ref);
		}
	}
}



附註:默認是帶有一個int類型的參數,但是在postNotification時,可以只發送消息不帶參數,如果你的函數不需要這個參數的話,所以到這裏已經完善了,坐等測試。

3.測試樣例

測試就更簡單了,類似於cocos2dx 的__NotificationCenter使用方式,先定義一個類,不用繼承任何類,當然也可以隨便繼承

Sprite.h

#pragma once
#ifndef _SPRITE_H_
#define _SPRITE_H_
class Sprite
{
public:
	Sprite();
	~Sprite();
	 void test(int _ref);
	 void dosomthing(int _ref);
};#endif

Sprite.cpp
#include "Sprite.h"
#include <iostream>
#include"NotificationCenter.h"
Sprite::Sprite()
{
	/*this->dosomthing();
	NotificationCenter::getInstance()->addObserver(dynamic_cast<Ref*>(this), "test", callfunc_selector(Sprite::test));
	NotificationCenter::getInstance()->PostNotification("test");*/
}
Sprite::~Sprite()
{
}
void Sprite::test(int _ref)
{
	cout << "I am here :"<<_ref<<endl;
	return;
}
void Sprite::dosomthing(int _ref)
{
	printf("I am here to doing somthing :%d",_ref);
	return;
}

再看看Main函數:
// C++Test.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include"Sprite.h"
#include"NotificationCenter.h"
using namespace std;
using namespace stdext;
void MainTest()
{
	auto sprite=new Sprite();
	NotificationCenter::getInstance()->addObserver(bind(&Sprite::test,sprite,_1), "test");//bind 的第一個參數是sprite的test成員函數的引用,sprite是對象,_1是std::placeholders的類型,_1表示第一個參數,以此類推,_2是第二個參數,所以可以不光傳一個int類型的,可以傳多個參數,只是需要自己改NotificationCenter類
	NotificationCenter::getInstance()->addObserver(bind(&Sprite::dosomthing,sprite,_1), "dosomthing");

	NotificationCenter::getInstance()->PostNotification("test");
	NotificationCenter::getInstance()->PostNotification("dosomthing", 123);
	delete sprite;
}

int _tmain(int argc, _TCHAR* argv[])
{
	MainTest();
	return 0;
}

運行視圖:
Over,其實一般寫消息機制,回調函數,大概用function是比較常見的吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章