关于《游戏创造》文章的错误

近日读〈游戏创造〉2006.3期中“设计模式与游戏开发”一文,文章主要讲的是设计模式在游戏开发中的应用,并举了3个实例,其中第三个关于“游戏对象访问控制”中作者提出使用 Proxy 模式来解决对象访问中的“野指针”问题,并给出相应的代码。当我看到这里时,我产生了疑问:
  1. 对于句柄的使用,文中说效率会有影响,值得商榷。但据我测试,如果使用 stl hash map 来存储的话,队列中有 1 亿个对象,每次访问 1 万个不同对象总计时间不会超过 0.0008s(PIV 3.0G),更何况游戏中的队列几乎不会有这么大的数量,所以使用 hash map 从效率来说还是可以满足要求的。
  2. 文中提出用 Proxy 模式解决问题,但是 Proxy 模式的提出并不是为了解决访问控制的问题的,而是屏蔽掉对象细节,比如两种不同的数据库 API 我们希望能做到通过统一接口去访问,而不是针对每种 API 来写不同的实现方式,这时就可以使用 Proxy 模式。所以在使用的模式方面,我对作者的观点产生了怀疑。我反复仔细看了作者附上了实现代码,总的来说就是对象和 Proxy 都保存彼此的指针,并且都指向自己,而且还保存引用其他对象的 Proxy 指针列表,这样当某个对象销毁时,会通知在其内部的代理对象,其他引用该对象的对象再进行访问时,首先访问其 Proxy 指针,如果 Proxy 指针内部的对象指针为空,则对象不能访问。但是问题在于,Proxy 中只保存唯一的一个被代理对象的指针(这是关键,因为如果存在多个指针的话,还是会存在野指针的问题),也就决定了对象的 Proxy 只能被一个对象所引用,这样当出现多个对象引用同一对象时,依然会出现 野指针的问题。如果我说得不清楚的话,可以通过下面的代码进行测试,为了说明问题,我简化并去掉了原文代码中不相关的部分。
 
template < typename T >
class Proxy
{
public:
 T* m_p;
 Proxy( T* p ) : m_p(p) {  if ( p ) p->SetProxy( this ); }
 void Reset() {  m_p = NULL; }
};
template <typename T>
class Proxiable
{
 Proxy<T>* m_proxy;
public:
 Proxiable() : m_proxy(NULL){}
 virtual ~Proxiable(){ if ( m_proxy ) m_proxy->Reset(); }
 void SetProxy( Proxy<T>* proxy ){ m_proxy = proxy; }
};
class Player : public Proxiable<Player>
{
 char* m_name;
 Proxy<Player>* m_player;
public:
 Player(char* name) : m_name(name),m_player(NULL){}
 void AddRef( Player* p){ m_player = new Proxy<Player>(p); }
 char* GetName(){ return m_name; }
 void UseRef(){ if ( m_player->m_p ) printf(m_player->m_p->GetName());}
};
int _tmain(int argc, _TCHAR* argv[])
{
 Player* p1 = new Player("p1\n");
 Player* p2 = new Player("p2\n");
 Player* p3 = new Player("p3\n");
 p2->AddRef( p1 );
 p3->AddRef( p1 );
 delete p1;
 p2->UseRef();
 p3->UseRef();
 return 0;
}
 
程序会 crash 在红色标注处。当然也有改进方法,就是在每个对象中加入一个 Proxy 列表,这样当对象销毁时,迭代列表每一项,实际上就是 mvc 的通知方式。不过这样不仅编写代码麻烦,而且每个需要这样功能的类要从 Proxyable 继承,拖泥带水,很是丑陋。更难以忍受的是 每个对象要多出 n*4 个空间来存储 Proxy 指针,并没有达到作者“轻量”的目的。所以综合考虑,句柄方式来实现对象访问控制是目前比较好的选择,而且 hash 算法也保证了查找效率,这也是 windows 操作系统内部标准的对象访问控制机制。
Advertisements
此条目发表在图书分类目录。将固定链接加入收藏夹。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s