Programmation Générique - C++

Master M2 Informatique --- Projet

Sérialisation

Principe

On se propose d'écrire une petite bibliothèque qui permette de sérialiser des objets, c'est-à-dire de les transformer en séquence binaire pour les sauvegarder. Une telle bibliothèque existe par exemple dans Boost, mais ici, on se propose d'écrire quelque chose d'à la fois beaucoup plus simple et de beaucoup moins puissant.

Travail demandé

On définira un type SerialWriter, dont le constructeur prend en argument un flot sortant, et qui contient une méthode write qui prend en argument un objet de n'importe quel type et l'envoie dans le flot ainsi que, récursivement tous ses champs sérialisés (voir plus loin). On maintiendra une table des adresses de manière à savoir, étant donné une adresse et un type (pourquoi l'adresse seule ne suffit-elle pas ? Bonne question !), si l'objet situé à cette adresse a déjà été sérialisé et de façon à remplacer dans la sérialisation les adresses mémoires par des indices. Ainsi, si un champ sérialisé de l'objet x est un pointeur, dans la sérialisation de x, on n'indiquera pas la valeur de l'adresse, mais l'ordre de l'objet pointé dans le flot des objets sérialisés.

De manière symétrique, on définira un type SerialReader, dont le constructeur prend en argument un flot entrant, et qui contient des méthodes paramétrées T* read_alloc() et void read(T& val); qui permettent de récupérer un objet dans le flot si on suppose son type connu. La première de ces méthodes allouera l'objet avec new, la seconde suppose que val est une référence sur un endroit déjà alloué.

Pour tous les types, la sérialisation d'un objet x consiste d'abord à envoyer dans le flot l'identifiant du type de x obtenu par typeid. Pour les types de base (int, double, etc...) la sérialisation consiste ensuite simplement à envoyer l'objet (non formaté) dans le flot, pour les autres, on spécifiera pour chaque type quels sont les champs qui sont sérialisés (ils ne le sont pas forcément tous).

Ceci sera spécifié pour chaque type sérialisable T par une classe Serialize qui contient les méthodes suivantes:

On ne fait aucune supposition sur les constructeurs des types à sérialiser; on suppose que leurs prototypes sont connus lorsqu'on écrit la spécialisation de la classe Serialize. Vous pourrez avoir besoin d'écrire une fonction (ou méthode) qui permet d'allouer de la mémoire avec new pour stocker un objet d'un certain type sans pouvoir appeler ses constructeurs...

Une des difficultés de la sérialisation et de la désérialisation réside dans le fait qu'un champ pointeur ou référence peut correspondre à un objet d'un sous-type que celui spécifié dans le champ. À cet effet, pour tout type T sérialisable, on aura accès à une classe SubTypes<T> définissant un type type_t donnant la liste des sous-types (sérialisables) concrets de T (T compris éventuellement). Noter que le comportement du programme sera légèrement différent si on est dans le cadre d'une hiérarchie polymorphe (avec des virtual) ou dans le cadre d'une hiérarchie statique, mais qu'une programmation correcte devrait rendre ceci transparent aussi bien au niveau du mécanisme de sérialisation que du comportement de votre module.

On écrira le programme de sorte que rajouter un type T sérialisable consiste uniquement en la spécialisation de Serialize, et la spécifications des sous-types sérialisables de T.

On pourra aussi maintenir une liste de types sérialisables afin que l'écriture échoue si elle est appliquée à un type non sérialisable.

Afin de tester la sérialisation, on définira une classe abstraite Node ayant deux sous-classes TriNode et IntNode, les objets de ces classes sont des nœuds d'un graphe orienté, un TriNode peut avoir au plus trois voisins (il a donc trois champs de type Node*, certains pouvant être nuls) et un IntNode a au plus un voisin, mais stocke une valeur entière. On testera la sérialisation sur des objets de ces classes. Un tel graphe est représenté par un nœud à partir duquel tous les nœuds sont (directement ou indirectement) accessibles. On doit bien sûr pouvoir écrirer Node* n=s.read_alloc<Node>() (s est ici un SerialReader) même si l'objet réellement stocké est d'un sous-type.

Rendu

Le projet est à rendre au plus tard le 24 février 2012. Il comptera pour moitié dans la note finale .

Le rendu du projet consiste en un mail dont le sujet est "Projet C++ M2"; le corps du mail contient les noms des auteurs (2 par projet). En pièce jointe, on enverra un fichier zip de nom CPP_XXX.zip ou CPP_XXX_YYY.zip où XXX et YYY sont les noms des auteurs.

Cette archive contient un fichier pdf de nom CPP_XXX.pdf ou CPP_XXX_YYY.pdf ainsi qu'un répertoire CPP_XXX_YYY à l'intérieur duquel se trouve les sources (commentées) du projet (réparties dans des répertoires bien chosis), un éventuel fichier README, un Makefile et un fichier Doxyfile, de sorte que "make" compile une démonstration du projet et "doxygen" produise une documentation (dans un sous-répertoire doc).

Le fichier pdf n'a pas besoin d'être très long; il présente votre travail, vos difficultés, vos choix, vos améliorations par rapport au sujet. Vous pouvez discuter des limitations de ce module, les éventuelles configurations qui empêchent de l'utiliser, les perspectives pour alléger l'ajout de classes sérialisables, voire la possibilité de rendre la sérialisation automatique sans besoin de spécifier les classes sérialisables...