2010-07-16 5 views
9

Comment désérialisez-vous une classe dérivée des données sérialisées? Ou peut-être devrais-je dire, existe-t-il un meilleur moyen de «désérialiser» les données dans les classes dérivées? Par exemple, supposons que vous ayez une classe de base virtuelle pure (B) héritée par trois autres classes, X, Y et Z. De plus, nous avons une méthode, serialize(), qui traduira X: B, Y: B et Z: B en données sérialisées. De cette façon, il peut être zappé sur un socket, un tube nommé, etc. vers un processus distant.Comment désérialisez-vous une classe dérivée des données sérialisées?

Le problème que j'ai, est, comment pouvons-nous créer un objet approprié à partir des données sérialisées?

La seule solution que je peux trouver est d'inclure un identifiant dans les données sérialisées qui indique le type d'objet dérivé final. Lorsque le destinataire analyse d'abord le champ de type dérivé à partir des données sérialisées, puis utilise une instruction switch (ou une sorte de logique de ce type) pour appeler le constructeur approprié.

Par exemple:

B deserialize(serial_data) 
{ 
    parse the derived type from the serial_data 

    switch (derived type) 
     case X 
      return X(serial_data) 
     case Y 
      return Y(serial_data) 
     case Z 
      return Z(serial_data) 
} 

donc après avoir appris le type d'objet dérivé nous invoquons le constructeur de type dérivé de approprié.

Cependant, cela semble gênant et lourd. J'espère qu'il y a une façon plus éloquente de le faire. Y a-t-il?

+5

sérialisation Compact (bit à bit) peut mordre à droite dans le cul, surtout si vous voulez sauvegarder les choses dans les fichiers. Au fil des années et des changements de classes, vous êtes à peu près foutu quand vous essayez de les charger à nouveau. Pour cette raison, faire des fichiers auto-documentés et versionnés est une bonne idée. Seulement si le client et le serveur sont en accord à tout moment sur le protocole utilisé, est-il correct d'envoyer des octets directs. Si non, alors j'utiliserais 'XML/JSON'. Cependant, je voudrais aussi regarder dans des choses qui peuvent rendre cela plus facile, comme «SOAP», etc –

+0

@Hamish +1, en faire une réponse! –

Répondre

2

En fait, c'est un problème plus général que la sérialisation, appelé Virtual Constructor.

L'approche traditionnelle est un Factory qui, basé sur un ID, renvoie le type dérivé droit. Il y a deux solutions:

  • la méthode switch comme vous avez remarqué, mais vous devez allouer sur le tas
  • la méthode prototype

La méthode prototype va comme ceci:

// Cloneability 
class Base 
{ 
public: 
    virtual Base* clone() const = 0; 
}; 

class Derived: public Base 
{ 
public: 
    virtual Derived* clone() const { return new Derived(*this); } 
}; 

// Factory 
class Factory 
{ 
public: 
    Base* get(std::string const& id) const; 
    void set(std::string const& id, Base* exemplar); 

private: 
    typedef std::map < std::string, Base* > exemplars_type; 
    exemplars_type mExemplars; 
}; 

Il est quelque peu traditionnel de faire du Factory un singleton, mais c'est une toute autre affaire.

Pour la désérialisation proprement dite, il est plus facile de faire appel à une méthode virtuelle deserialize pour appeler l'objet.

EDIT: Comment fonctionne l'usine?

En C++, vous ne pouvez pas créer un type que vous ne connaissez pas.L'idée ci-dessus est donc que la tâche de construction d'un objet Derived est donnée à la classe Derived, au moyen de la méthode clone.

Vient ensuite le Factory. Nous allons utiliser un map qui va associer un "tag" (par exemple "Derived") à une instance d'un objet (disons Derived ici).

Factory factory; 
Derived derived; 
factory.set("Derived", &derived); 

Maintenant, quand nous voulons créer un objet type que nous ne savons pas au moment de la compilation (parce que le type est décidé à la volée), on passe une étiquette à l'usine et demander un objet revenir.

std::unique_ptr<Base> base = factory.get("Derived"); 

Sous le couvert, le Factory trouvera le Base* associé à l'étiquette "Derived" et invoquer la méthode clone de l'objet. Cela crée (ici) un objet de type d'exécution Derived.

Nous pouvons le vérifier en utilisant l'opérateur typeid:

assert(typeid(base) == typeid(Derived)); 
+0

Pardonnez mon ignorance. Mais je ne comprends pas comment une usine résout le problème. En fait, je suis tombé du camion à la classe d'usine ci-dessus. –

+0

Pas de problème, nous sommes tous là pour apprendre, j'ai ajouté une explication sur la façon dont l'usine résout le problème de construction virtuelle. Ne soyez pas confus par la structure 'map', c'est juste un commutateur sophistiqué qui peut être rempli au moment de l'exécution. –

+0

Merci pour les informations supplémentaires. Donc, si je comprends bien. Nous avons toujours besoin d'une sorte d'étiquette pour nous dire de quel type d'objet il s'agit. L'usine utilise cette balise pour trouver la fonction "registered" qui renvoie un nouveau clone. Corrigez-moi si je me trompe, mais cela ressemble à une instruction de commutation "dynamique". Au lieu d'avoir un interrupteur codé dur comme je l'ai ci-dessus, l'usine vous donne un moyen "flexible" de fournir la même fonctionnalité? Ou est-ce que je manque quelque chose? BTW, MERCI! J'ai déjà adapté cette solution pour un autre problème que j'avais! –

0
 
inmemory: 
-------- 
type1 { 
    chartype a; 
    inttype b; 
}; 
serialize(new type1()); 

serialized(ignore { and ,): 
--------------------------- 
type1id,len{chartypeid,adata,inttypeid,bdata} 

je suppose, dans un protocole de sérialisation idéal, tous les types non primitifs doivent être préfixé avec typeid, len. Même si vous sérialisez un seul type qui n'est pas dérivé de quelque chose, vous devez ajouter un identifiant de type, car l'autre extrémité doit savoir quel type obtient (indépendamment de la structure d'héritage). Vous devez donc mentionner les identifiants de classes dérivées dans la sérialisation, car, logiquement, ce sont des types différents. Corrigez-moi si je me trompe.

+0

C'est en quelque sorte la direction que je prenais, mais je ne pense pas que la longueur soit nécessaire. Bien sûr, cela dépend de la définition de la définition de type. Je l'envisageais comme identifiant le type de classe, qui en déduirait aussi la longueur. Mais je pense que ma réflexion est également basée sur le fait que j'utilise les files d'attente de messages POSIX pour la transmission de sorte que la couche de transport connaît la longueur. –

Questions connexes