Warum machen wir, mich eingeschlossen, so einfache Probleme immer so kompliziert?
Vielleicht liege ich hier falsch. Aber ich muss mich fragen: Ist dies wirklich das beste Design für Ihre Bedürfnisse?
Im Großen und Ganzen kann die reine Funktionsvererbung besser durch Funktions-/Methodenzeiger oder Aggregation/Delegation und die Weitergabe von Datenobjekten erreicht werden als durch Polymorphismus.
Polymorphismus ist ein sehr mächtiges und nützliches Werkzeug. Aber es ist nur eines von vielen Werkzeugen, die uns zur Verfügung stehen.
Es sieht so aus, dass jede Unterklasse von Packet ihren eigenen Marshalling- und Unmarshalling-Code benötigt. Vielleicht den Marshalling/Unmarshalling-Code von Packet erben? Vielleicht ihn erweitern? Alles zusätzlich zu handle() und was sonst noch benötigt wird.
Das ist eine Menge Code.
Obwohl es wesentlich umständlicher ist, könnte es kürzer und schneller sein, die Daten von Packet als struct/union-Attribut der Klasse Packet zu implementieren.
Das Ein- und Ausmarschieren würde dann zentralisiert.
Abhängig von Ihrer Architektur könnte es so einfach sein wie write(&data). Vorausgesetzt, es gibt keine Big/Little-Endian-Probleme zwischen Ihren Client/Server-Systemen und keine Probleme mit dem Auffüllen. (Z.B. sizeof(data) ist auf beiden Systemen gleich.)
Schreiben(&Daten)/Lesen(&Daten) ist eine fehleranfällige Technik . Aber es ist oft ein sehr schneller Weg, den ersten Entwurf zu schreiben. Später, wenn es die Zeit erlaubt, können Sie ihn durch individuellen, attributtypbasierten Marshalling/Unmarshalling-Code ersetzen.
Auch: Ich habe mir angewöhnt, Daten, die gesendet/empfangen werden, als struct zu speichern. Sie können eine Struktur mit operator=() bitweise kopieren, was manchmal SEHR hilfreich war! Obwohl vielleicht nicht so sehr in diesem Fall.
Letztendlich werden Sie eine Schalter Anweisung irgendwo auf diesem Unterklassen-Id-Typ. Die Factory-Technik (die an sich schon sehr leistungsfähig und nützlich ist) übernimmt diese Umschaltung für Sie, indem sie die erforderliche clone()- oder copy()-Methode bzw. das entsprechende Objekt nachschlägt.
OR können Sie das in Packet selbst tun. Sie könnten einfach etwas so Einfaches wie verwenden:
( getHandlerPointer( id ) ) ( this )
Ein weiterer Vorteil dieses plumpen Ansatzes (Funktionszeiger), abgesehen von der schnellen Entwicklungszeit, ist, dass Sie nicht ständig ein neues Objekt für jedes Paket zuweisen und löschen müssen. Sie können ein einzelnes Paketobjekt immer wieder verwenden. Oder einen Vektor von Paketen, wenn man sie in eine Warteschlange stellen will. (Allerdings würde ich das Packet-Objekt löschen, bevor ich read() erneut aufrufe! Nur um sicher zu gehen...)
Je nach der Dichte des Netzwerkverkehrs in Ihrem Spiel kann die Zuweisung/Deallokation teuer werden. Andererseits, Vorzeitige Optimierung ist die Wurzel allen Übels. Und Sie können auch einfach Ihre eigenen Operatoren für das Neu-/Löschen erstellen. (Noch mehr Codierungsaufwand...)
Was Sie (mit Funktionszeigern) verlieren, ist die saubere Trennung der einzelnen Pakettypen. Insbesondere die Möglichkeit, neue Pakettypen hinzuzufügen, ohne bereits vorhandenen Code/Dateien zu ändern.
Beispiel-Code:
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: Du könntest mit Google herumstöbern und cdecl/c++decl ausfindig machen. Das sind sehr nützliche Programme. Besonders wenn man mit Funktionszeigern herumspielt.
Z.B.:
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