自從上次寫完借(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:
Sprite.cpp:
// 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;
}
運行視圖: