游戏开发中的数据池问题和改进

来源:互联网 发布:通达信 引用贝格数据 编辑:程序博客网 时间:2024/06/05 09:49

之前开发游戏的时候,为了能够在任意地方都能访问到数据,一般的做法是建立一个数据池,然后把想要的数据全部放到数据池类中。数据池的写法一般是建一个名为DataPool的单例类,通常情况下对单例的实现一般是这样的

DataPool.h

#pragma onceclass DataPool{public:    static DataPool* getInstance();};
DataPool.cpp

#include <stdio.h>#include "DataPool.h"DataPool* g_instance = NULL;DataPool* DataPool::getInstance(){    if(NULL == g_instance){g_instance = new DataPool();    }    return g_instance;}

为了方便演示数据池的工作方式,以下用游戏中常会涉及到用户账户信息和登录信息进行举例。通常情况下会建立两个类,这边暂时定为AccountInfo和LoginInfo。简单实现一下,类似下面:

用户信息类AccountInfo

AccountInfo.h

#pragma onceclass AccountInfo{public:    AccountInfo(void);    ~AccountInfo(void);};

AccountInfo.cpp

#include "AccountInfo.h"AccountInfo::AccountInfo(void){} AccountInfo::~AccountInfo(void){}
登录信息类LoginInfo

LoginInfo.h

#pragma onceclass LoginInfo{public:    LoginInfo(void);    ~LoginInfo(void);};
LoginInfo.cpp

#include "LoginInfo.h"LoginInfo::LoginInfo(void){}LoginInfo::~LoginInfo(void){}

既然这两个类建好了,那么下一步就是把它们丢到DataPool中,所以DataPool的代码也相应的更改

#pragma once#include "AccountInfo.h"#include "LoginInfo.h"class DataPool{public:   static DataPool* getInstance();public:   AccountInfo m_AccountInfo;   LoginInfom_LoginInfo;};
这边说的丢到DataPool中,其实就是把这两个类的实例作为DataPool的成员变量,并且把访问方式定位public,以后要访问的时候一般是

DataPool* pool = DataPool::getInstance();AccountInfo& account =pool->m_AccountInfo;LoginInfo& login = pool->m_LoginInfo;

也就是说所有的数据都要通过DataPool的单例访问,这样AccountInfo和LoginInfo作为DataPool的成员变量,也就拥有了单例的性质。同时也不用去管AccountInfo和LoginInfo的内存释放,因为他们的是以栈上对象的方式来声明的,其生命周期是随着DataPool的单例来管控的。DataPool什么时候释放,它们也跟着什么时候释放。

当然这样做法问题也是非常多的,比方说如果一个不小心把引用符号&去掉了,那么数据就会被拷贝一份。想象一下下面的写法:

LoginInfo login = pool->m_LoginInfo;//这边LoginInfo不是声明为LoginInfo&方式的,所以LoginInfo的数据就被拷贝

还有一个更让人头疼的问题是,由于把所有的数据都丢到DataPool中,那么相应的也得把这些数据类的头文件也包含到DataPool的头文件中,像上面的

#pragma once//看到了没,需要这两个头文件#include "AccountInfo.h"#include "LoginInfo.h"class DataPool{public:   static DataPool* getInstance();public:   AccountInfo m_AccountInfo;   LoginInfo   m_LoginInfo;};
包含头文件本身没什么问题。问题是DataPool在开发过程中会被很多人修改,一般会有3到5个人在改它。而游戏中基本上每个业务逻辑类都要用到它,这样就造成了一个问题,DataPool随便一修改,相应的会有上百个文件也一起被编译。在没用联合编译工具的情况下,最可观的等待编译的时间是半个小时吧。

这些都是以前用在游戏开发的实现方案,一般是谁用谁苦逼。为此,需要对其进行改进。改进的技巧是用到了类成员模板函数以及typeid这个运行时运算符。

typeid这个运算符能够根据类型来返回类型的名称,举个例子

const char* name = typeid(AccountInfo).name();printf(name);//输出结果将是class AccountInfoname = typeid(LoginInfo).name();printf(name);//输出结果将是class LoginInfo

接着看看DataPool改进后的样子

#pragma once#include <stdio.h>#include <typeinfo>#include <map>#include <string>using namespace std;class DataPool{public:static DataPool* getInstance();public:template<class T> T* get(){const char* name = typeid(T).name();std::map<std::string, void*>::iterator target = m_mapInstance.find(name);if(target != m_mapInstance.end()){return (T*)target->second;}else{T* instance = new T();m_mapInstance.insert(make_pair(name, instance));return instance;}return NULL;}protected:std::map<std::string, void*> m_mapInstance;};
它的用法是

DataPool* pool = DataPool::getInstance();LoginInfo* info = pool->get<LoginInfo>();LoginInfo* info2 = pool->get<LoginInfo>();AccountInfo* account = pool->get<AccountInfo>();

在上面例子中info和info2这两个指针所指的对象是一样的,因为DataPool中对相同类型的数据进行了缓存。这样就可以在不改动DataPool的情况下来全局访问自己想要的数据,只要你通过get这个类成员模板函数。

现在看看get()模板函数的实现,这个函数的用途就是根据模板参数T以及typeid来取得该模板参数的类型名称

const char* name = typeid(T).name();//正如前面对typeid讲解的那样,这边将根据T的类型来返回T所对应的类型名称
然后再在map中寻找之前这个模板参数的是否有缓存过,有的话直接用之前的实例。也就是这段代码

std::map<std::string, void*>::iterator target = m_mapInstance.find(name);if(target != m_mapInstance.end()){return (T*)target->second;}
如果没有的,那么就创建一个实例,然后把它加入到map中

else{ T* instance = new T(); m_mapInstance.insert(make_pair(name, instance));//map的key为类型名称,value为分配出来的实例。 return instance;}
通过以上改进之后,DataPool就不在依赖于特定数据的头文件了,同时也让DataPool固定下来,不会有大改动。自此,改进完成,有需要的各位可以试试。

0 0
原创粉丝点击