-
Notifications
You must be signed in to change notification settings - Fork 16
/
serialization.txt
312 lines (270 loc) · 22.1 KB
/
serialization.txt
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
SERIALIZATION
/=+===============================+=\
/ : : \
)==: INTRO :==(
\ :_______________________________: /
\=+===============================+=/
BUTS ==> #Sauvegarder dans un fichier ("archive") l'état d'une CLASS_VAR ("objet") afin de :
# - la restaurer ultérieurement (persisting objects)
# - la transmettre sur un réseau
# - la transmettre à un autre process/thread (IPC)
# - détecter différences de states d'un objet à travers le temps (dont logging)
#Seuls les CLASSDT sont sauvegardées, car les CLASSFK sont immuables en C++.
#Ancienne manière : unions. Cependant, cela n'est pas architecture-independent.
DONNES PAR BOOST ==> # - Architecture independent : par exemple marche même si endianness du write et du read diffèrent.
# - Références et pointeurs :
# - unswizzling : transforme * et & en un format, qui lors de la récupération (swizzling) récupère
# les datas, mais avec des * et & différents (deep copy, conservant les shared data en plus).
# De plus si pointeur polymorphique, sauvegarde le type polymorphique.
#Désavantages :
# - l'ensemble de l'objet doit être lu ou écrit pour lire ou écrire l'une de ses parties.
# Plus simple, mais problème de performance si l'on veut utiliser qu'une partie.
# - les CLASSDT non publiques sont aussi manipulées, rendant la manipulation non-backward compatible
# en cas de changement des détails de l'implémentation :
# - résolu par boost::serialization :
# - on choisit les CLASSDT à manipuler
# - versioning
AUTRE ==> #Format de l'archive :
# - binaire, implementation-dependent.
# - human-readable standards/known formats : XML, YAML, JSON
#Autres noms : deflating ; marshalling.
#Contraire : deserialize, inflating, unmarshalling.
BOOST ==> # - Library : libboost_[w]serialisation[-mt]
# - Namespace : boost::serialization (boost::archive pour les *archive, archive_flags et exceptions)
# - Headers :
# - <boost/archive/*archive.hpp> : *archive
# - <boost/archive/basic_archive.hpp> : archive_flags
# - <boost/serialization/serialization.hpp> : le principal, dont serialize(), access,
# BOOST_CLASS_VERSION, split_*
# - <boost/serialization/base_object.hpp> : base_object
# - <boost/serialization/export.hpp> : BOOST_CLASS_EXPORT*
# - <boost/serialization/binary_object.hpp> : binary_object
# - <boost/serialization/nvp.hpp> : *NVP et *nvp
# - <boost/serialization/{vector,deque,list,
# map,set,string,complex,shared_ptr,
# scoped_ptr,weak_ptr,valarray,bitset,
# variant,utility,optional,hash_map,
# hash_set,array}.hpp> : serialization avec ces types
DESIGN ==> #Séparation de :
# - implémentation de serialize()
# - types d'archives, qui implémentent serialize() des types fondamentaux de manière spécifique.
# - ex : text_archive, XML, binary
# - bien saisir la différence entre :
# - ARCHIVE & (ou << ou >>) VAL, hors d'un serialize(), qui manipule une archive entière.
# Préférer utiliser << et >>.
# - ARCHIVE & (ou << ou >>) VAL, dans un serialize(), qui incrémente/décrémente une archive.
# Préférer utiliser &.
/=+===============================+=\
/ : : \
)==: IN/DECREMENTATION ARCHIVE :==(
\ :_______________________________: /
\=+===============================+=/
RENDRE UN OBJET SERIALIZABLE
Mettre :
- dans la définition de l'objet (intrusif, préférer cela) :
- template <class T> void serialize( T& ar, unsigned int const& version ) { ar & CLASSDT1 ; ar & CLASSDT2 ; etc. }
- l'ordre des & importe bien sûr.
- utiliser & permet de n'avoir qu'un seul serialize() plutôt que de diviser en deux load() et save() utilisant respectivement << et >> : préférer cela
- serialize() toujours private, et définir friend class boost::serialization::access pour que <<, >> et & puissent accéder à serialize()
- Ou (non intrusif), en dehors de la définition de l'objet (attention CLASSDT doivent être accessibles) :
- template <class T> void serialize( T& ar, OBJET& objet, unsigned int const& version ) { ar & objet.CLASSDT1 ; ar & objet.CLASSDT2 ; etc. }
SERIALIZABLECONCEPT
- Les CLASSDT utilisés via &, << ou >> doivent respecter SerializableConcept :
- ou builtin type (hors *)
- ou un builtin-array de SerializableConcept
- ou ADR d'un SerializableConcept non-builtin
- ou une classe définissant serialize() tel que ci-dessus
- pour une classe à template, l'instantiation de son template doit en plus respecter SerializableConcept
- dans le dernier cas, le serialize() de l'objet est appelé, de manière récursive. Il faut donc définir ce dernier si ce n'est pas le cas. Au bout du compte les << et >> sur builtin types sont effectués :
- & et << écrit le builtin type sur l'archive (incrémentation de l'archive)
- & et >> affecte la valeur à la CLASSDT dans l'objet (décrémentation de l'archive)
Cela est a dissocié de ARCHIVE << OBJET, ou >>, qui écrit/lit l'ensemble de l'archive, tandis que >> ou << dans serialize() ne le fait que pour le prochain morceau de l'archive.
- des serialize() ont déjà été définies dans la bibliothèque pour les standard containers (dont std::basic_string, std::bitset et std::valarray), boost::{shared,weak,scoped}_ptr, std::complex, std::pair, boost::variant, boost::optional, boost::hash_{map,set},boost::array
- d'autres sont définies avec boost::date_time, boost::uuid
- &, >> et << renvoie ARCHIVE. On peut donc les enchaîner (avec des newlines esthétiques) comme std::cout << ... << ...
SEPARER LOAD ET SAVE
Séparer load() et save() (à éviter tant que possible) :
- si << et >> doivent invoquer deux serialize() différents :
- par exemple, nouvelle version << toujours dernière version, donc pas de check de version contrairement à >>
- autre exemple : si l'affectation ou l'obtention des CLASSDT provoque des side effects (par ex. shared_ptr)
- alors :
- version intrusive :
- définir en private load() et save() au lieu de serialize(), même signature (et save est const { })
- invoquer, sans serialize(), boost::serialization::split_member( T_VAR, *this, UINT_VAL ), qui invoquera à son tour this->load( T_VAR, UINT_VAL ) ou this->save( T_VAR, UINT_VAL )
- plutôt que de définir : serialize( ARGS ) { ...::split_free( ARGS ) }, on peut mettre la macro BOOST_SERIALIZATION_SPLIT_MEMBER()
- version non-intrusive :
- définir load() et save() en dehors de la classe, même signature que serialize() non-intrusif
- invoquer split_free(), non split_member()
- utiliser BOOST_SERIALIZATION_SPLIT_FREE(class_type), dans namespace globale, et non ...SPLIT_MEMBER
HERITAGE
- classe enfant devant définir un serialize() doit invoquer :
- ou si l'on veut invoquer le serialize() de son parent : ar & base_object<parent>(*this)
- ou sinon : void_cast_register( static_cast<enfant*>( NULL ), static_cast<parent*>( NULL ) );
- et :
- la sauvegarde des CLASSDT différant de ceux de son parent
- si virtual base classe d'un héritage en forme de diamand (multiple inhéritance), faire :
- BOOST_CLASS_TRACKING( class, track_never )
VERSIONING
- si modification de CLASSDT, besoin de nouveau serialize() -> problème de backward-compatibility
- differents serialize() suivant la version utilisée :
- dernier paramètre de serialize() est "version" qu'on peut utiliser :
- if ( version >= 3 ) ar & CLASSDT_DEFINI_A_PARTIR_VERSION_3
- ce paramètre est passé lors de &, << et >> :
- pour écriture : écrit dans l'archive. Si macro BOOST_CLASS_VERSION(objet, NUMBER) (dans namespace global), version NUMBER pour objet. Sinon 0.
- pour lecture : lit l'archive
- si pas utilisé, enlever nom "version" après unsigned int pour supprimer warning "unused parameter"
- si l'on tente de >> une archive ayant une version supérieure à la classe à charger, une exception unsupported_class_version est lancée
IMPLEMENTATION LEVEL
- faire BOOST_CLASS_IMPLEMENTATION( class, LEVEL ), change LEVEL de class, où LEVEL est l'un de :
- not_serializable : ne peut pas être sérialiser. Défaut des volatile VAR.
- primitive_type : Défaut des builtins types
- object_serializable : comme object_class_info, mais sans les infos de version : le versioning est donc non disponible, au profit d'une légère meilleure efficience.
- object_class_info : Défaut des non-builtin types.
/=+===============================+=\
/ : : \
)==: TYPES PARTICULIERS :==(
\ :_______________________________: /
\=+===============================+=/
POINTEURS DE TYPES BUILTINS (HORS ARRAY STATIQUES)
1) ne sont pas Serializable. Solution :
a) I) Définir un wrapper autour du type builtin BUILTIN, avec un operator de transtypage TYPE. Cela peut rendre nécessaire l'utilisation d'un transtypage explicite WRAPPER_VAR = (WRAPPER)BUILTIN_VAR.
Cela peut être fait avec la macro : BOOST_STRONG_TYPEDEF( BUILTIN, WRAPPER )
II) Définir ensuite serialize() non-intrusif avec :
- WRAPPER& comme deuxième argument
- effectuant : ARCHIVE & (BUILTIN&)WRAPPER_VAR
III) Exemple d'implémentation pour float :
BOOST_STRONG_TYPEDEF( float, tracked_float )
template <class T>
void serialize( T& ar, tracked_float& b, unsigned int const& )
{ ar & (float&)b; }
IV) Il semble nécessaire de rajouter BOOST_CLASS_IS_WRAPPER( BUILTIN ) pour éviter une erreur lors de l'utilisation de
BOOST_SERIALIZATION_NVP( ) et de xml_*archive
b) Pour un pointeur d'array dynamique, on peut aussi utiliser binary_object, qui est Serializable et optimisé :
- il s'instantie avec ( VOID_ADR, SIZE_T_VAL ), représentant le pointeur de l'array dynamique et sa taille (n'effectue pas l'allocation mémoire, il faut le faire soi-même). make_binary_object( VOID_ADR, SIZE_T_VAL ) renvoie également un binary_object.
- BINARY_OBJECT.m_t et BINARY_OBJECT.m_size renvoie ces deux paramètres.
c) Il y a aussi boost::serialization::array<T> et make_array, qui sont comme binary_object, sauf qu'ils utilisent non pas un VOID_ADR mais un T_ADR, et que m_t est address(), et m_size est count(). L'utiliser pour wrapper un vector
2) invoquent le default constructor lors de la restauration. En théorie, cela veut dire qu'ils sont donc initialisé à 0, et que la valeur pointée n'est pas conservée. Mais cela semble marcher pour moi. Je mets quand même la solution si un problème survient, et aussi parce que cela valable dans les cas où l'on veuille modifier le default constructor invoqué :
- définir template <class T> void boost::serialization::save_construct_data( T& ARCHIVE, WRAPPER const* const ADR, unsigned int const& version ), définissant la sauvegarde dans ARCHIVE de *ADR, par exemple : ARCHIVE << (TYPE&)*ADR
- définir load_construct_data (même signature, sans premier des 3 const), définissant la restauration dans *ADR, par exemple : TYPE VAL ; ARCHIVE >> VAL ; ::new (ADR) TYPE(VAL);
- exemple d'implémentation pour tracked_float :
namespace boost { namespace serialization {
template <class T>
void save_construct_data( T& ar, tracked_float const* const b, unsigned int const& )
{ ar << (float&)*b; }
template <class T>
void load_construct_data( T& ar, tracked_float* const b, unsigned int const& )
{ float c;
ar >> c;
::new ( b ) float( c ); } } }
TOUS LES POINTEURS (BUILTINS OU NON)
- si sauvegarde de plusieurs ADR identiques, un seul objet est créé/restauré, et tous les pointeurs pointent vers lui (utilisation pour cela de tags, "object tracking")
- on peut manipuler des pointeurs NULL
- un *ADR doit avoir un default constructor :
- sinon, définir des load/save_construct_data(). Pour les pointeurs non-builtins TYPE* :
- save_construct_data() doit mimer action de save()
- et load() doit instantier un TYPE, mimer l'action de load() sur lieu, puis faire ::new ( b ) TYPE( TYPE_VAR )
- tentative de sérialiser VAR et *ADR (ADR étant &VAR) lance une exception pointer_conflict
POINTEUR POLYMORPHIQUES
- penser à base_object<parent>(*this) ou void_cast_register (cf ci-dessus)
- faire :
- ou dans le serialize() manipulant les pointeurs polymorphiques BASE_ADR pour type polymorphique ENFANT possible :
- ARCHIVE.register_type( static_cast<ENFANT*>( NULL ) );
- ou dans le namespace global, pour chaque ENFANT possible :
- BOOST_CLASS_EXPORT_KEY( ENFANT ) dans l'header file, après la définition d'ENFANT
- BOOST_CLASS_EXPORT_IMPLEMENT( ENFANT ) dans le fichier manipulant le pointeur polymorphique, avant la manipulation, et après l'include de l'header file
A ne faire qu'une fois pour une classe ENFANT donnée, contrairement au précédent, qui doit être fait pour chaque serialize() pouvant utilisant ENFANT
- sinon exception unregistered_class est lancée
- si manipulation de pointeur polymorphique BASE_ADR, où BASE est une classe abstraite, faire :
- BOOST_SERIALIZATION_ASSUME_ABSTRACT( BASE )
RÉFÉRENCES
- semblent marcher sans effort particulier, bien que documentation dit de les traiter comme les pointeurs
CONSTNESS
- en théorie, une CLASS contenant des CLASSDT const, ou une const CLASS, ne devrait pas être restaurée via la serialization >> car cela implique une modification (mais la sauvegarde << ne pose pas de problème). Toutefois, on peut admettre pouvoir modifier une CLASSDT const dans le seul cas d'une récupération. Dans ce cas :
- pour des const CLASSDT :
- dans serialize() ou load(), faire ARCHIVE & const_cast<T&>( T_VAR ) au lieu de ARCHIVE & T_VAR
- pour une const CLASS_VAR (archive entière) :
- même chose, mais en plus :
- mettre load en const { }
- faire la récupération via ARCHIVE >> const_cast<CLASS&>( CLASS_VAR )
/=+===============================+=\
/ : : \
)==: MANIPULATION ENSEMBLE ARCHIVE :==(
\ :_______________________________: /
\=+===============================+=/
MANIPULATION DE L'ENSEMBLE DE L'ARCHIVE
- Ecriture :
- construire *_OARCHIVE avec un STD::OSTREAM
- *_OARCHIVE << OBJET (ou &, préférer <<)
- le tout dans une fonction anonyme pour que *_OARCHIVE soit détruit avant STD::OFSTREAM
- Lecture :
- même chose mais avec ISTREAM, IARCHIVE et >>
- lance une exception archive_exception si IARCHIVE construit avec un ISTREAM dont le format est corrompu (i.e. n'étant pas la représentation d'une archive)
- si exception lancée, risque de memory leaking. Pour avoir exception-safety :
- toutes CLASSDT récursivement doivent être exception-safe
- mettre un try/catch et mettre ARCHIVE.delete_created_pointers() dans le catch
- boost::serialization permet de choisir les CLASSDT à manipuler dans l'OBJET. Par conséquent on peut choisir seulement l'interface publique, ou moins, ou plus.
CONCEPTS
- SavingArchiveConcept (X respecte SerializableConcept) :
- T::is_saving renvoie boost::mpl::bool_<true>
- T::is_loading renvoie boost::mpl::bool_<false>
- T_VAL << X_VAL et T_VAL & X_VAL (même comportement). Renvoie T_VAL&.
- T.save_binary(X_ADR, SIZE_T_VAL) : enregistre octets allant de *X_ADR à X_ADR[SIZE_T_VAL - 1]
- T.register_type<x>(void) et T.register_type(X_ADR) (même effet)
- T.get_library_version(void) renvoie la version de boost::serialization compatible (différent du versioning des objets).
- LoadingArchiveConcept :
- comme SavingArchiveConcept, sauf que :
- is_saving et is_loading renvoient l'inverse
- "save_binary" est "load_binary", << est >>
- T.reset_object_address(X_ADR1, X_ADR2) : signale que X_ADR1 a été déplacé à X_ADR2. Renvoie void. Utilisé pour l'object tracking.
- T.delete_created_pointers(void) : supprime l'espace alloué aux pointeurs lors de >> (à invoquer lors d'une exception)
- Archives ne sont pas des streams, cependant ils prennent souvent des streams dans leur constructor.
ARCHIVES DÉFINIS DANS LA LIBRARY
- 5 classes {text,xml,binary}_[w]oarchive, et leurs correspondants en *iarchive
- utiliser concepts, pas l'inhéritance car :
- pas de base classe entre oarchive et iarchive
- multiple inhéritance rend difficile l'utilisation de base classes. Chaque classe dérive de deux classes :
- basic_{text,xml,binary}_oarchive<T>, dérivant tous de common_oarchive<T> (T est un CRTP)
- basic_text_oprimitives<T> et basic_binary_oprimitive<T,U,V> (pas de lien entre les deux, le deuxième est utilisé seulement par binary_*archive), le template indiquant le char_type.
- Constructors :
- BINARY_*ARCHIVE( STD::[O|I]STREAMBUF[, ARCHIVE_FLAGS] )
- TEXT_*ARCHIVE( STD::[O|I]STREAM[, ARCHIVE_FLAGS] )
- XML_*ARCHIVE( STD::[O|I]STREAM[, ARCHIVE_FLAGS] )
- ARCHIVE_FLAGS est par défaut une absence de flags parmi (or'd), et n'est que pour les *oarchive :
- no_header, supprime header de l'archive (à éviter car peut contenir des infos sur endianness, etc.)
- no_codecvt, fait que l'archive ne peut pas modifier (temporairement toujours) std::codecvt
- no_xml_tag_checking, fait qu'aucune exception n'est lancée en cas de corruption d'une archive XML
- Destructor restaure facets éventuellement modifiées du STREAM : à invoquer donc avant destructor du STREAM (d'où blocs anonymes)
TYPES D'ARCHIVES
- text_*archive : sous forme de texte human-readable
- binary_*archive : STREAM associé doit avoir flag ios::binary, plus compacte
- xml_*archive :
- le nom des tags doit être indiqué. On peut en général utiliser le nom des classes. Utilise donc ces tags et non les types des CLASSDT, contrairement aux autres archives. Dans tous les cas, faire :
- dans serialize(), mais aussi pour la manipulation de l'objet entier : ... & make_nvp( STR, VAL ) au lieu de ... & VAL (ou <<, ou >>)
- BOOST_SERIALIZATION_NVP( VAL ) équivaut à make_nvp avec "VAL" comme STR
- BOOST_SERIALIZATION_BASE_OBJECT_NVP( TYPE ) équivaut à make_nvp( "TYPE", base_object<TYPE>( *this ) )
- n'a pas d'incidence si archive n'est pas xml_*archive : le faire donc toujours si l'on ignore le type de l'archive utilisé
- une exception archive_exception est lancée si le fichier XML a été corrompu ou ne correspond plus à l'état actuel de l'OBJET (versioning mal utilisé)
CUSTOM ARCHIVES
- il faut les enregistrer via BOOST_ARCHIVE_CUSTOM_ARCHIVE_TYPES
- cf doc.
/=+===============================+=\
/ : : \
)==: AUTRE :==(
\ :_______________________________: /
\=+===============================+=/
Portabilité :
- attentions que taille des types sérialisés en écriture soient les mêmes que ceux en lecture (ex : char vs wchar_t)
- text_*archive et xml_oarchive utilisent la locale pour le char_type, et xml_warchive utilise toujours wchar_t
- MinGW a des problèmes avec les wstreams
- VC++ :
- 7.1- : doit utiliser flags activant le RTTI
- 7.0- : xml_*archive doivent utiliser version 1.6- de boost::spirit
- sérialisation de shared_ptr : définir macro BOOST_SERIALIZATION_SHARED_PTR( TYPE )
- pour std::map : BOOST_BROKEN_COMPILER_TYPE_TRAITS_SPECIALIZATION( KEY_TYPE )
- Borland 5.64- :
- pas de sérialisation d'enum
- même chose que VC++ pour xml_*archive
- Comeau 4.3.3- :
- problème avec codecvt facet