关于统一存储多种子类型对象到定长共享内存的有关问题

关于统一存储多种子类型对象到定长共享内存的问题

关于统一存储多种子类型对象到定长共享内存的问题

缘起

在项目组的游戏服务器中,有一系列的对象,它们派生自同一个基类,比如Role为Player,Monster的基类等。
各个派生类会有自己独特的属性及方法,这是必然。但因这些对象大小不一致,我们不可能将其放到一个定长的内存池中。

先前粗暴的做法用了两种:
* 将Player中的一些属性都放置在Role中。  
* 将Monster等使用另一个内存池存放。
产生的问题是:
* 结构的层次不清。  
* 因分配的内存池不同,需要创建几份内存池。
* 实现所有Role统一编号(id)更麻烦。

当一些东西看不下去的时候,内心有个强烈的意愿候要改变它,优化它,怎么办呢?

尝试

在尝试之前,了解当前的问题,并且明确要得到什么样的结果。那么

明确目标
  1. 统一所有类型的对象分配自一个内存池。
  2. 将对原有代码的修改控制在一定范围内。
对象分配自一个内存池

关于这点,我想到一个办法,使用Union来让其它的子类型都包含其中,这样产生的新类型其对象的内存即可认为是我们要让内存池去分配的大小。于是写了如下代码:

typedef union _RoleExtend
 {
    Role role_;
    Player player_;
    Tank tank_;
    Monster monster_;
 }RoleExtend;
模版类的难题

因为业务需要,负责分配内存的模版类也负责生成唯一id,它需要模版参数(即我们的子类)提供类似GetID(),SetID(uint)类似的方法,也可能会有Recover()类似的用于服务器重启时恢复对象数据的方法。所以我们的Role结构形如:

class Role{
    GetID();
    SetID(uint);
    Recover();
};

而一旦我们使用上述的 RoleExtend 的话,编译器又会因为 这只鸭子不像鸭子 而报错。如何让RoleExend具有这些方法呢? 基于这个想法,当时让我绝望得几乎窒息。于是微博中有记录这么一句话:
关于统一存储多种子类型对象到定长共享内存的有关问题

晚上把笔记本带回了家,盯着源码看着发呆,突然灵光一现,即然在模版类中要这些本来属于Role的方法而我的RoleExtend不能给,何不把Role也作为参数传过去,当需要调用这些时,先强转为Role后再调用,一切岂不so easy?!

是的,方法就这么简单,可是我却曾经困扰了几个小时:)

Union的困惑

以为一切就这么简单,没想到编译器这时候跳出来闹腾了,不让你这么干。 它说道:

typedef union _RoleExtend
 {
    Role role_;
    Player player_;
    Tank tank_;
    Monster monster_;
 }RoleExtend;

constructor not allowed in union 
好吧,我觉得是我遗忘了union的用法了,里面只能存放POD类型。而我们的Role,Monster里有一些存在构造函数的类等,于是乎不被允许放到Union中。前进的第一步就遇上了绊脚石。(新C++0x已经允许这么做了)

在查找这个问题的时候,也找到了解决这个问题的钥匙,那就是使用boost::varient即可。

虽然我们不需要用于boost:varient的任何方法,但是它提供的将多种类型置入其中的这个容器效果,就可解决union的问题。

于是乎简单如下的一条定义即可:
typedef boost::variant<Role, Player, Tank, Monster> RoleExtend;

好的,现在编译器乖了,不闹了。

代码的修改控制在一定范围内

做了两点,我认为是比较好的实现了控制修发代码量的问题。

  • 在模版参数中使用默认参数

    • 因为这个模版类在好几个地方使用到,不仅是管理Role的,使旧代码一行不改即可用。
    • 原参数<typename Role> 修改为 <typename Role, typename RoleExtend = Role>
  • 添加一层适配层RoleMgr

    • 将对于Role的内存分配变为RoleExtend的实现隐藏
    • 原调用的几个方法add(),get(),del()实现,并新增GetPlayer(),GetTank()等接口,以要求显式获得某类型。

至此,这个小改动算完成。

后续可改进点

  • 可添加更严格的类型判断和检测。
  • 使重启恢复时调用不同类型的Recover。(目前无需求)
  • 是否可以利用boost::varient类型的运行时判定来做点文章?