基类角色之对象管理器
版本:0.1
最后修改:2009-07-02
撰写:李现民
问题描述
C++程序设计中,保存一个生命周期不是由类对象自己维护的其它对象的指针通常是个坏主意,因为程序逻辑很难判断在使用该指针的时刻其所指对象是否已经被销毁。这种应用需求很常见,例如在网游设计中,由于华丽的装备加载需要进行大量硬盘I/O,因此加载过程通常由一个独立的加载线程执行,由于在装备加载完成的时刻该玩家很可能已经下线,因此加载线程就需要能够去判断此时玩家对象是否仍然有效。
为了解决该问题,通常会设计一个PlayerManager类用于跟踪管理当前所有的玩家对象,而加载线程通过提供玩家id以确认该玩家对象仍然存在。此种设计方案需要一个独立的PlayerManager类,并提供一个全局的PlayerManager类对象以跟踪当前的所有玩家对象。
出于代码复用的目的,我希望实现一个通用基类解决此类问题。该基类需要为子类对象至少提供以下几方面的能力:
-
为所有的对象分配一个全局唯一的index,通过该index能够(尽可能快的)获取到拥有该index的类对象(或NULL);
-
自动跟踪类对象的生成与销毁,不需要手工编写额外代码;
-
实现迭代器,提供遍历当前所有有效对象的能力;
-
提供“移除”接口,使得对象可以主动要求放弃被对象管理器跟踪;
-
各子类实现拥有完全独立的管理器逻辑;
解决方案
将实现代码保存为objectman.hpp,内容如下:
/********************************************************************
created: 2009-06-29
author: lixianmin
purpose: base class for object manager
Copyright (C) 2009 - All Rights Reserved
*********************************************************************/
#ifndef _LIB_OBJECT_MAN_HPP_INCLUDED_
#define _LIB_OBJECT_MAN_HPP_INCLUDED_
#include <cassert>
#include <map>
namespace lib
{
template<typename T>
class objectman
{
public:
typedef int index_t; // 索引类型
typedef std::map<index_t, T*> object_map; // 容器类型
enum { INVAID_INDEX= 0}; // 无效索引
public:
// 迭代器
class iterator
{
public:
iterator(void): _iter(_mObjects.begin()){} // 构造函数
bool has_next(void) const { return (_mObjects.end()!= _iter); } // 测试是否还有下一个对象
T* next(void) { return has_next()? (_iter++->second): NULL; } // 获取下一个对象指针
private:
typename object_map::iterator _iter;
};
public:
// 构造函数
objectman(void)
{
enable_index();
}
// copy 构造函数
objectman(const objectman& rhs)
{
enable_index();
}
// 析构函数
virtual ~objectman(void)
{
disable_index();
}
// 赋值操作符
objectman& operator= (const objectman& rhs)
{
}
// 通过索引获取对象
static T* get_by_index(index_t index)
{
object_map::iterator iter= _mObjects.find(index);
if (_mObjects.end()!= iter)
{
T* pObject= iter->second;
assert(NULL!= pObject);
return pObject;
}
return NULL;
}
// 获取对象索引
index_t get_index(void) const { return _idxObject; }
// 生成索引(使能被对象管理器遍历到)
void enable_index(void)
{
_idxObject= ++_idxGenderator;
assert(get_index()!= INVAID_INDEX);
assert(_mObjects.find(get_index())== _mObjects.end());
_mObjects.insert(std::make_pair(get_index(), static_cast<T*>(this)));
}
// 移除索引(使不能被对象管理器遍历到)
void disable_index(void)
{
if (get_index()!= INVAID_INDEX)
{
assert(_mObjects.find(get_index())!= _mObjects.end());
_mObjects.erase(get_index());
_idxObject= INVAID_INDEX;
}
}
private:
friend class iterator;
static object_map _mObjects; // 对象容器
static index_t _idxGenderator; // 索引发生器
index_t _idxObject; // 对象索引
};
template<typename T>
typename objectman<T>::object_map objectman<T>::_mObjects;
template<typename T>
typename objectman<T>::index_t objectman<T>::_idxGenderator= 0;
}
#endif
测试代码
测试代码如下:
#include <cassert>
#include "objectman.hpp"
// 声明一个类
class Player:public lib::objectman<Player>
{
};
int main(int argc, char* argv[])
{
const int idxDisabled= 5;
// 生成对象
for (int i= 0; i< 10; ++i)
{
Player* pPlayer= new Player;
if (idxDisabled== pPlayer->get_index())
{
// 从对象管理器中移除该对象
pPlayer->disable_index();
}
}
//使用迭代器遍历类对象
Player::iterator iter;
while(iter.has_next())
{
Player* pPlayer= iter.next();
const int idxPlayer= pPlayer->get_index();
// 断言之:遍历不到已经移除的对象
assert(idxPlayer!= idxDisabled);
// 断言之:可以通过idxPerson取得对象指针
assert(pPlayer== Player::get_by_index(idxPlayer));
// 回收对象
delete pPlayer;
pPlayer= NULL;
}
// 断言之:所有对象均已被删除
Player::iterator iter2;
assert(!iter2.has_next());
system("pause");
return 0;
}
vs2008下所有断言均动作通过。
已知问题
-
在大量生成对象的情况下,index索引空间(代码定义为int的范围)有可能使用殆尽,甚至产生重复,这会导致两个对象拥有相同index的严重错误;
-
std::map的查找速度不是特别另人满意;