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

本文详细阐述了如何改进数据池技术在游戏开发中的应用,通过使用类成员模板函数和 typeid 运算符,实现了数据全局访问的同时减少了对特定数据头文件的依赖,提高了开发效率并降低了编译时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

DataPool.h

#pragma once
class 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 once
class AccountInfo
{
public:
    AccountInfo(void);
    ~AccountInfo(void);
};

AccountInfo.cpp

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

LoginInfo.h

#pragma once
class 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;
   LoginInfo	m_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 AccountInfo
name = 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固定下来,不会有大改动。自此,改进完成,有需要的各位可以试试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值