C'est le procédé de sérialisation le plus simple. Prenons un exemple pour décrire ce procédé : la classe
Note, qui représente la note d'un devoir. Il existe différents types de notes : note sur 20, note sur 40…
Préparatifs
Voici la classe Note :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | class Note
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & numerateur;
ar & denominateur;
}
int numerateur;
int denominateur;
public:
Note() {};
Note(int n, int d) :
numerateur(n), denominateur(d){}
};
|
C'est un code plutôt habituel, sauf les lignes 4 à 10 que je vais expliquer.
Code : C++ | friend class boost::serialization::access;
|
La méthode que nous utilisons ici est dite intrusive : nous modifions la classe pour la préparer à la sérialisation. Nous verrons dans la prochaine partie une méthode non intrusive (pas de modification de la classe).
Nous déclarons donc comme classe amie
boost::serialization::access
: étant donné qu'il s'agit d'une classe
friend, Boost pourra récupérer le contenu de la classe pour le sérialiser. Sans cette porte dérobée, l'encapsulation ne nous aurait pas laissé accéder aux variables dont la portée est
private
.
Code : C++ | template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & numerateur;
ar & denominateur;
}
|
La première chose à remarquer est la présence d'un
template pour la classe
Archive : c'est tout à fait normal. En effet, la sérialisation de Boost permet de stocker les objets dans des fichiers texte, des fichiers XML… Nous avons donc besoin de généricité. La méthode
serialize sera automatiquement appelée lors de l'archivage. Nous devons déclarer toutes les variables de la classe dans cette méthode, ou elles ne seront pas sérialisées.
Note concernant l'opérateur & : c'est une utilisation intéressante de la surcharge qui est utilisée ici. Lors de l'archivage, & sera équivalent à <<, et lors de la restauration, il sera équivalent à >>. Vous comprendrez tout lorsque vous verrez le code source complet.
Sérialisation
Voici le code complet de cet exemple :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 | #include <fstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
class Note
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & numerateur;
ar & denominateur;
}
int numerateur;
int denominateur;
public:
Note() {};
Note(int n, int d) :
numerateur(n), denominateur(d) {}
};
int main()
{
std::ofstream ofs("fichierDeSerialisation");
// Vous avez vu comme je travaille bien ? :)
const Note maNoteDePhysisque(20,20);
{
boost::archive::text_oarchive oa(ofs);
oa << maNoteDePhysisque;
}
/** Quelque temps plus tard… ***/
Note monAncienneNote;
{
std::ifstream ifs("fichierDeSerialisation");
boost::archive::text_iarchive ia(ifs);
ia >> monAncienneNote;
}
return 0;
}
|
Qu'est-ce qui change ?
Nous avons inclus des
headers spécifiques à la sérialisation aux lignes 3 et 4. Nous avons aussi inclus
fstream, qui servira lors du stockage dans un fichier.
Nous avons ajouté un
main qui sérialise et désérialise. Observons-le plus en détail :
Code : C++28
29
30
31
32
33
34
35
36
37 | std::ofstream ofs("fichierDeSerialisation");
// Vous avez vu comme je travaille bien ? :)
const Note maNoteDePhysisque(20,20);
{
boost::archive::text_oarchive oa(ofs);
oa << maNoteDePhysisque;
}
|
On ouvre d'abord un fichier pour stocker notre objet à la ligne 28. Puis nous créons à la ligne 31 un objet Note. La vraie sérialisation commence dans le bloc des lignes 34 à 37 : on utilise notre fichier comme une
text_archive (ligne 35), puis on fait simplement appel à l'opérateur
<< pour stocker
maNoteDePhysisque dans l'archive. À la fin du bloc, les destructeurs sont automatiquement appelés, et l'archive est refermée (attention cependant, vous ne devez pas détruire le flux de fichier
oa car l'archive s'en charge !).
J'ai délimité une portée (j'ai utilisé des accolades ouvrantes et fermantes sans en avoir vraiment besoin) pour appeler les destructeurs le plus rapidement possible, ce qui permet d'être sûr que les données sont bien sauvegardées immédiatement. Vous pouvez tout à fait choisir de ne pas le faire.
Code : C++ | Note monAncienneNote;
{
std::ifstream ifs("fichierDeSerialisation");
boost::archive::text_iarchive ia(ifs);
ia >> monAncienneNote;
}
|
Nous allons désérialiser. Notez que cette opération n'a pas forcément lieu dans le même programme, ni sur le même ordinateur : vous pouvez très bien envoyer le fichier par le réseau puis l'ouvrir sur une autre machine. Le résultat sera exactement le même.
Nous créons un objet vide, puis, dans le bloc, nous ouvrons notre fichier de sérialisation comme une
text_archive et nous extrayons son contenu dans l'objet précédemment créé. À la fin du bloc, les destructeurs sont automatiquement appelés, et les fichiers refermés.
Comme vous le voyez, la syntaxe est très simple : << pour stocker, >> pour récupérer.
Vous pouvez utiliser cette méthode sur des classes plus complexes. Imaginons que vous vouliez créer une classe
Bulletin :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | class Bulletin
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & note1;
ar & note2;
}
Note note1;
Note note2;
protected:
Bulletin(const Note & n1_, const Note & n2_) :
note1(n1_), note2(n2_) {}
public:
Bulletin() {}
virtual ~Bulletin() {}
};
|
Ce n'est pas un problème d'avoir des classes imbriquées dans la classe à sérialiser (dans le cas présent, Bulletin contient Note) ; cependant, il faut que chaque classe soit elle-même sérialisable.