要让Factory类提前不了解所有类型,您需要提供每个类自我注册的单例。我总是得到用于定义模板类的静态成员的语法错误,所以不要只剪切和粘贴它:
class Packet { ... }; typedef Packet* (*packet_creator)(); class Factory { public: bool add_type(int id, packet_creator) { map_[id] = packet_creator; return true; } }; template<typename T> class register_with_factory { public: static Packet * create() { return new T; } static bool registered; }; template<typename T> bool register_with_factory<T>::registered = Factory::add_type(T::id(), create); class MyPacket : private register_with_factory<MyPacket>, public Packet { //... your stuff here... static int id() { return /* some number that you decide */; } };
您需要查找工厂模式。
工厂查看输入数据并为您创建正确类的对象。
对于复制,您需要编写克隆函数,因为构造函数不能是虚拟的:
virtual Packet * clone() const = 0;
每个Packet实现如下所示:
virtual Packet * clone() const { return new StatePacket(*this); }
例如StatePacket。数据包类应该是不可变的。收到数据包后,其数据可以被复制或丢弃。因此不需要赋值运算符。使赋值运算符为私有,不要定义它,这将有效地禁止分配包。
对于反序列化,您使用工厂模式:创建一个类,该类在给定消息ID的情况下创建正确的消息类型。为此,您可以在已知消息ID上使用switch语句,也可以使用以下映射:
struct MessageFactory { std::map<Packet::IdType, Packet (*)()> map; MessageFactory() { map[StatePacket::Id] = &StatePacket::createInstance; // ... all other } Packet * createInstance(Packet::IdType id) { return map[id](); } } globalMessageFactory;
实际上,你应该添加一个检查,比如id是否真的已知和这样的东西。这只是一个粗略的想法。
为什么我们(包括我自己)总是让这么简单的问题如此复杂?
也许我不在这里。但我不禁要问:这真的是最适合您需求的设计吗?
总的来说,通过函数/方法指针,聚合/委托以及数据对象的传递,而不是通过多态,可以更好地实现仅函数继承。
多态性是一种非常强大且有用的工具。但它只是我们可用的众多工具之一。
看起来Packet的每个子类都需要自己的编组和解组编码。也许继承Packet的编组/解编码?也许延伸它?全部在handle()之上,还有其他所需的东西。
这是很多代码。
虽然更多的kludgey,它可能会更短&amp;更快地将Packet的数据实现为Packet类的struct / union属性。
然后集中编组和解组。
根据您的架构,它可以像写(和数据)一样简单。假设您的客户端/服务器系统之间没有大/小端问题,并且没有填充问题。 (例如,sizeof(data)在两个系统上都是相同的。)
写(安培;数据)/读(安培;数据) 的 是一种容易出错的技术 强> 。但是编写初稿通常是一种非常快捷的方式。稍后,当时间允许时,您可以将其替换为基于每个属性类型的编组/解组编码。
也: 我已经将存储/接收的数据存储为结构。您可以使用operator =()按位复制结构,这有时非常有用!虽然在这种情况下可能不是那么多。
最终,你将有一个 开关 语句在某个子类-id类型上。工厂技术(它本身非常强大和有用)为您进行此切换,查找必要的clone()或copy()方法/对象。
的 要么 强> 你可以在Packet中自己做。你可以使用一些简单的东西:
(getHandlerPointer(id))(this)
除了快速开发时间之外,这种kludgey(函数指针)方法的另一个优点是,您不需要为每个数据包不断地分配和删除新对象。您可以反复重复使用单个数据包对象。或者如果要对数据包进行排队,则为数据包矢量。 (请注意,在再次调用read()之前我会清除Packet对象!为了安全...)
根据您游戏的网络流量密度,分配/取消分配可能会变得昂贵。然后, 的 过早优化是万恶之源。 强> 而且您可以随时滚动自己的新/删除操作符。 (然而更多的编码开销...)
丢失的(使用函数指针)是每种数据包类型的清晰隔离。特别是能够在不改变预先存在的代码/文件的情况下添加新的数据包类型。
示例代码:
class Packet { public: enum PACKET_TYPES { STATE_PACKET = 0, PAUSE_REQUEST_PACKET, MAXIMUM_PACKET_TYPES, FIRST_PACKET_TYPE = STATE_PACKET }; typedef bool ( * HandlerType ) ( const Packet & ); protected: /* Note: Initialize handlers to NULL when declared! */ static HandlerType handlers [ MAXIMUM_PACKET_TYPES ]; static HandlerType getHandler( int thePacketType ) { // My own assert macro... UASSERT( thePacketType, >=, FIRST_PACKET_TYPE ); UASSERT( thePacketType, <, MAXIMUM_PACKET_TYPES ); UASSERT( handlers [ thePacketType ], !=, HandlerType(NULL) ); return handlers [ thePacketType ]; } protected: struct Data { // Common data to all packets. int number; int type; union { struct { int foo; } statePacket; struct { int bar; } pauseRequestPacket; } u; } data; public: //... bool readFromSocket() { /*read(&data); */ } // Unmarshal bool writeToSocket() { /*write(&data);*/ } // Marshal bool handle() { return ( getHandler( data.type ) ) ( * this ); } }; /* class Packet */
PS:你可能会去谷歌并抓住cdecl / c ++ decl。它们是非常有用的程序。特别是在玩函数指针时。
例如。:
c++decl> declare foo as function(int) returning pointer to function returning void void (*foo(int ))() c++decl> explain void (* getHandler( int ))( const int & ); declare getHandler as function (int) returning pointer to function (reference to const int) returning void