对于我的游戏,我有一个Packet类,它代表网络数据包,基本上由一组数据和一些纯虚函数组成
然后我想要从Packet派生类,例如:StatePacket,PauseRequestPacket等。这些子类中的每一个都将实现虚拟函数Handle(),当网络引擎被调用时收到这些数据包以便它可以完成它的工作,有几个get / set函数可以读取和设置数据数组中的字段。
所以我有两个问题:
如果有人想要任何澄清,请问。
- 谢谢
编辑:我对此并不满意,但这就是我管理的内容:
Packet.h:http://pastebin.com/f512e52f1
Packet.cpp:http://pastebin.com/f5d535d19
PacketFactory.h:http://pastebin.com/f29b7d637
PacketFactory.cpp:http://pastebin.com/f689edd9b
PacketAcknowledge.h:http://pastebin.com/f50f13d6f
PacketAcknowledge.cpp:http://pastebin.com/f62d34eef
如果有人有时间查看并提出任何改进建议,我会感激不尽。
是的,我知道工厂模式,但我如何编码它来构建每个类?一个巨大的转换声明?这也会重复每个类的ID(一次在工厂,一个在序列化器中),我想避免。
答案 0 :(得分:8)
对于复制,您需要编写克隆函数,因为构造函数不能是虚拟的:
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是否真的已知并且这样的东西。这只是一个粗略的想法。
答案 1 :(得分:0)
您需要查找工厂模式。
工厂查看传入的数据并为您创建正确类的对象。
答案 2 :(得分:0)
要让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 */; }
};
答案 3 :(得分:0)
为什么我们(包括我自己)总是让这么简单的问题如此复杂?
也许我不在这里。但我不禁要问:这真的是最适合您需求的设计吗?
总的来说,通过函数/方法指针,聚合/委托以及传递数据对象,而不是通过多态,可以更好地实现仅函数继承。
多态性是一种非常强大且有用的工具。但它只是我们可用的众多工具之一。
看起来Packet的每个子类都需要自己的编组和解组编码。也许继承Packet的编组/解编码?也许延伸它?全部在handle()之上,还有其他所需的东西。
这是很多代码。
虽然更多的kludgey,它可能会更短&amp;更快地将Packet的数据实现为Packet类的struct / union属性。
然后集中编组和解组。
根据您的架构,它可以像写(和数据)一样简单。假设您的客户端/服务器系统之间没有大/小端问题,并且没有填充问题。 (例如,sizeof(data)在两个系统上都是相同的。)
写入(和数据)/读取(和数据)是一种容易出错的技术。但是编写初稿通常是一种非常快捷的方式。稍后,当时间允许时,您可以将其替换为基于每个属性类型的编组/解组编码。
另外:我已经将存储/接收的数据存储为结构。您可以使用operator =()按位复制结构,这有时非常有用!虽然在这种情况下可能没那么多。
最终,您将在该子类-id类型的某处获得 switch 语句。工厂技术(它本身非常强大和有用)可以为您切换,查找必要的clone()或copy()方法/对象。
或你可以在Packet中自己做。你可以使用简单的东西:
(getHandlerPointer(id))(this)
除了快速开发时间之外,这种方法(函数指针)的另一个优点是,您不需要为每个数据包不断地分配和删除新对象。您可以反复重复使用单个数据包对象。或者如果要对数据包进行排队,则为数据包矢量。 (请注意,我会在再次调用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 */
E.g:
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