使用boost::any实现的“泛用”消息体

C/C++中传统的消息体通常是以C struct实现的。在这种消息体中,第一个int型的字段标识了该消息的类型(通常是枚举值),第二个int型的字段标识了该消息体的长度,再往下才是消息体本身。这样的消息体效率很高,但有几个缺点。首先是消息体的长度是固定的,第二是消息体内容的格式也是固定的,并且需要在C/C++中定义。

我希望SaberCore是脚本驱动的,如果需要在C++中定义消息体结构,那就实在是太煞风景了。我想到的第一个解决方案是在Lua脚本中定义消息的结构,但这种方法实现起来麻烦用起来也麻烦。第二个解决方案是定义一种“泛用”型消息体,可以胜任任何种类的消息数据。

一提起“泛用”型消息体,第一个想到的就是Android中的intent,intent中可以附带数据extra。它之所以是泛用的,是因为extra不但是变长的,而且你可以向extra中put入任意类型的数据。因为Java所有对象都派生自共同的基类Object,并且所有东西都是new出来的,所以这点其实很容易做到。C/C++中最大的问题在于通常情况下同一个容器中不能放置不同类型的数据。当然我们可以在里面放void *指针,但new出来的东西需要被delete,我们总不能惦记着一个消息体用完还要delete吧?

下面隆重介绍解决方案:boost::any,当当当当!简单直白地说,boost::any可以让C++变成弱类型语言。假如我们定义了这样的一个容器:

std::vector<boost::any> v;

那么你就可以这样放置数据:

v.push_back(1);
v.push_back("我是字符串");
v.push_back(true);
v.push_back(std::vector<int>())

可以这样取出数据:

int a = boost::any_cast<int>(v[0]);
std::string b = boost::any_cast<std::string>(v[1]);
bool c = boost::any_cast<bool>(v[2]);
std::vector<int> d = boost::any_cast<std::vector<int>>(v[3]);

是不是很碉堡

有了这样的一个工具,泛用消息体就很容易定义了。下面是其代码实现:

class scEvent
{
private:
    // 使用字符串而不是枚举型来定义事件类型
    std::string mName; 
    // 使用key - value的方式来索引数据
    std::map<std::string, boost::any> mItems;
public:
    // 各种get/set函数...
};

有兴趣的可以看看boost::any的源码实现,不得不感叹写boost的那些高手怎么可以想到C++还能这么写。

这样的一个泛用型消息体缺点当然大大地有,并且主要是在效率上的。它的体积会比传统的消息体大上几倍,因此拷贝需要消耗更多的时间。同时使用字符串而不是枚举值来区分事件类型,字符串比较也会消耗很多的时间。但便利性和效率向来是难以共存的~也许当性能出现瓶颈时,我会考虑使用传统的消息体。但现在嘛~ 🙂

使用boost::any实现的“泛用”消息体》上有2条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注