Faire son propre système de sérialisation.
Salut,
étant donné les problèmes que j'ai eu avec certaines librairies de sérialisation en c++ lors de l'enregistrement des types pour les pointeurs sur des objets de classe dérivées.
de plus ces librairies sont parfois de grosses dépendances supplémentaire à fournir qui augment considérablement la taille de mon framework!
J'ai décidé de créer mon propre système de sérialisation d'objets.
L'idée est de passer en template le type réel du pointeur à une classe de base dont hériteront tout les objets sérializable, afin que après je n'ai plus qu'à faire un downcast et appeler la bonne fonction sérialize lors de l'archivation.
Donc j'ai fait une classe sérializable, son rôle est de contenir le type réel de l'objet. (c'est à dire celui fourni à l'instanciation et non celui fourni lors de la déclaration de l'objet à sérialiser)
Et de récupérer le bon pointeur vers la fonction membre sérialize c'est à dire celle de la classe dérivée si la classe est héritée), j'ai donc fait ceci :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
#ifndef SERIALIZATION
#define SERIALIZATION
namespace odfaeg {
template <typename C>
class Serializable {
public :
template <typename A>
void getSerializeFunc(void(C::**func)(A&)) {
*func = &C::serialize;
}
};
}
#endif // SERIALIZATION |
Ensuite j'ai fait deux classes (ITextArchive et OTextArchive) exactement comme certaines librairies le font :
Et je fais un downcast de la classe de base vers la classe dérivée, au cas ou le type de l'objet à sérialisé et le type de la classe contenant la fonction membre serialize sont différent.
Code:
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
|
#ifndef ODFAEG_ARCHIVE
#define ODFAEG_ARCHIVE
#include <vector>
#include <fstream>
#include "serialization.h"
namespace odfaeg {
class OTextArchive {
public :
OTextArchive(std::ofstream& buffer) : buffer(buffer) {
}
template <typename T>
operator& (T& data) {
buffer<<data;
}
template <typename T>
operator& (T* data) {
buffer<<*data;
}
template <typename T>
operator& (std::vector<T>& data) {
buffer<<data.size();
for (unsigned int i = 0; i < data.size(); i++)
buffer<<data[i];
}
template <typename T>
operator& (std::vector<T*> data) {
buffer<<data.size();
for (unsigned int i = 0; i < data.size(); i++)
buffer<<(*data[i]);
}
template <typename T>
void save (T object, void(T::*func)(OTextArchive&)) {
(object.*func)(*this);
}
template <typename B, typename D>
void save (B* base, void(D::*func)(OTextArchive&)) {
(static_cast<D*>(base)->*func)(*this);
}
template <typename C>
void operator<<(Serializable<C>* object) {
void(C::*func)(OTextArchive&);
object->getSerializeFunc(&func);
save(object, func);
}
private :
std::ofstream& buffer;
};
class ITextArchive {
public :
ITextArchive (std::ifstream& buffer) : buffer(buffer) {
}
template <typename T>
operator& (T& data) {
buffer>>data;
}
template <typename T>
operator& (T* data) {
data = new T();
buffer>>*data;
}
template <typename T>
operator& (std::vector<T>& data) {
int size;
buffer>>size;
for (unsigned int i = 0; i < size; i++)
buffer>>data[i];
}
template <typename T>
operator& (std::vector<T*> data) {
unsigned int size;
buffer>>size;
for (unsigned int i = 0; i < size; i++) {
T* vi = new T();
buffer>>(*vi);
data.push_back(vi);
}
}
template <typename T>
void load (T object, void(T::*func)(ITextArchive&)) {
(object.*func)(*this);
}
template <typename B, typename D>
void load (B* base, void(D::*func)(ITextArchive&)) {
(static_cast<D*>(base)->*func)(*this);
}
template <typename C>
void operator>>(Serializable<C>* object) {
void(C::*func)(ITextArchive&);
object->getSerializeFunc(&func);
load(object, func);
}
private :
std::ifstream& buffer;
};
}
#endif // ODFAEG_ARCHIVE |
Lors de la déclaration de ma classe de base, je dois passer en paramètre template le type réel de l'objet.
Code:
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
|
class BaseObjet : public odfaeg::Serializable<D> {
public :
BaseObjet(std::string name) {
this->name = name;
}
template <typename Archive>
void serialize (Archive & ar) {
ar & name;
}
virtual std::string getName() {
return name;
}
private:
std::string name;
};
class ObjetToutBete : public BaseObjet<ObjetToutBete> {
public:
ObjetToutBete(std::string name) : BaseObjet("Base objet de objet tout bete") {
this->name = name;
}
template <typename Archive>
void serialize (Archive & ar) {
BaseObjet::serialize(ar);
ar & name;
}
std::string getName() {
return BaseObjet::getName() + "\n" + name;
}
private :
std::string name;
}; |
Ensuite je dois lors de la déclaration, indiquer que le type réel du pointeur est ObjetToutBete.
Code:
1 2 3 4 5 6 7 8 9 10 11 12
|
int main(int argc, char* args[])
{
std::ofstream ofs("FichierDeSerialisation");
BaseObjet<ObjetToutBete>* ob = new ObjetToutBete("un objet tout bete");
odfaeg::OTextArchive oa(ofs);
oa<<ob;
std::ifstream ifs("FichierDeSerialisation");
odfaeg::ITextArchive ia(ifs);
ia>>ob;
std::cout<<ob->getName()<<std::endl;
} |
Ok ça marche et j'ai bien tout dans le fichier
Code:
1 2
|
Base objet de objet tout beteun objet tout bete |
Alors je me demandais si il n'y aurais pas moyen de faire mieux... (sans utiliser une "registration" de type car ça, ça ne marche pas très bien surtout quand j'ai une classe C qui dérive d'une classe B et ensuite une classe A qui dérive de B)
Car quand je passe BaseObjet en template ça me renvoie une erreur de compilation :
Code:
1 2
|
BaseObjet<BaseObjet>* ob = new BaseObjet("un objet de base"); |
Code:
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
|
||=== Build: Debug in ODFAEG-DEMO (compiler: GNU GCC Compiler) ===|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp||In function int main(int, char**):|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|81|error: type/value mismatch at argument 1 in template parameter list for template<class D> class BaseObjet|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|81|error: expected a type, got BaseObjet|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|81|error: invalid type in declaration before = token|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|81|error: expected type-specifier before BaseObjet|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|83|error: no match for operator<< (operand types are odfaeg::OTextArchive and int*)|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|83|note: candidates are:|
/usr/local/include/odfaeg/Core/archive.h|41|note: template<class C> void odfaeg::OTextArchive::operator<<(odfaeg::Serializable<C>*)|
/usr/local/include/odfaeg/Core/archive.h|41|note: template argument deduction/substitution failed:|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|83|note: mismatched types odfaeg::Serializable<C> and int|
/usr/local/include/odfaeg/Math/matrix3.h|147|note: std::ostream& odfaeg::operator<<(std::ostream&, const odfaeg::Matrix3f&)|
/usr/local/include/odfaeg/Math/matrix3.h|147|note: no known conversion for argument 1 from odfaeg::OTextArchive to std::ostream& {aka std::basic_ostream<char>&}|
/usr/local/include/odfaeg/Math/matrix4.h|165|note: std::ostream& odfaeg::operator<<(std::ostream&, const odfaeg::Matrix4f&)|
/usr/local/include/odfaeg/Math/matrix4.h|165|note: no known conversion for argument 1 from odfaeg::OTextArchive to std::ostream& {aka std::basic_ostream<char>&}|
/usr/local/include/odfaeg/Math/matrix2.h|119|note: std::ostream& odfaeg::operator<<(std::ostream&, const odfaeg::Matrix2f&)|
/usr/local/include/odfaeg/Math/matrix2.h|119|note: no known conversion for argument 1 from odfaeg::OTextArchive to std::ostream& {aka std::basic_ostream<char>&}|
/usr/local/include/odfaeg/Math/vec2f.h|205|note: std::ostream& odfaeg::operator<<(std::ostream&, const odfaeg::Vec2f&)|
/usr/local/include/odfaeg/Math/vec2f.h|205|note: no known conversion for argument 1 from odfaeg::OTextArchive to std::ostream& {aka std::basic_ostream<char>&}|
/usr/local/include/odfaeg/Math/vec4.h|241|note: std::ostream& odfaeg::operator<<(std::ostream&, const odfaeg::Vec3f&)|
/usr/local/include/odfaeg/Math/vec4.h|241|note: no known conversion for argument 1 from odfaeg::OTextArchive to std::ostream& {aka std::basic_ostream<char>&}|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|86|error: no match for operator>> (operand types are odfaeg::ITextArchive and int*)|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|86|note: candidates are:|
/usr/local/include/odfaeg/Core/archive.h|88|note: template<class C> void odfaeg::ITextArchive::operator>>(odfaeg::Serializable<C>*)|
/usr/local/include/odfaeg/Core/archive.h|88|note: template argument deduction/substitution failed:|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|86|note: mismatched types odfaeg::Serializable<C> and int|
/usr/local/include/odfaeg/Math/matrix4.h|166|note: std::istream& odfaeg::operator>>(std::istream&, odfaeg::Matrix4f&)|
/usr/local/include/odfaeg/Math/matrix4.h|166|note: no known conversion for argument 1 from odfaeg::ITextArchive to std::istream& {aka std::basic_istream<char>&}|
/home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|87|error: request for member getName in * ob, which is of non-class type int|
||=== Build failed: 7 error(s), 0 warning(s) (0 minute(s), 4 second(s)) ===| |
Et la seconde chose est que je n'arrive pas à mettre un int dans un std::streambuf et je n'ai pas trouvé beaucoup de documentation là dessus.
Alors voilà je voudrais savoir comment faire ça.
Merci d'avance.
Je pense que mon problème est enfin résolu!
Bon j'ai enfin trouvé, en fait il y a les objets ostream et istream qui sont les classe de bases de tout les streams d'entrée et de sortie, ça y es maintenant je pense que je peux sérialiser n'importe quel type d'objet ou bien de collection!
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
#ifndef SERIALIZATION
#define SERIALIZATION
namespace odfaeg {
template <typename C>
class Serializable {
public :
template <typename A>
void getSerializeFunc(void(C::**func)(A&)) {
*func = &C::serialize;
}
};
}
#endif // SERIALIZATION |
Code:
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
|
#ifndef ODFAEG_ARCHIVE
#define ODFAEG_ARCHIVE
#include <vector>
#include <ostream>
#include <istream>
#include "serialization.h"
namespace odfaeg {
class OTextArchive {
public :
OTextArchive(std::ostream& buffer) : buffer(buffer) {
}
template <typename T>
operator& (T& data) {
buffer<<data;
}
template <typename T>
operator& (T* data) {
bool found = false;
for (unsigned int i = 0; i < adresses.size() && !found; i++) {
if (data == adresses[i]) {
found = true;
}
}
if (!found) {
buffer<<*data;
adresses.push_back(data);
}
}
template <typename T>
operator& (std::vector<T>& data) {
std::size_t size = data.size();
buffer<<size;
for (unsigned int i = 0; i < data.size(); i++)
buffer<<data[i];
}
template <typename T>
operator& (std::vector<T*> data) {
std::size_t size = data.size();
buffer<<size;
for (unsigned int i = 0; i < data.size(); i++)
buffer<<(*data[i]);
}
template <typename T>
void save (T object, void(T::*func)(OTextArchive&)) {
(object.*func)(*this);
}
template <typename B, typename D>
void save (B* base, void(D::*func)(OTextArchive&)) {
(static_cast<D*>(base)->*func)(*this);
}
template <typename C>
void operator<<(Serializable<C>* object) {
void(C::*func)(OTextArchive&);
static_cast<C*>(object)->getSerializeFunc(&func);
save(object, func);
}
private :
std::ostream& buffer;
std::vector<unsigned long long int> adresses;
};
class ITextArchive {
public :
ITextArchive (std::istream& buffer) : buffer(buffer) {
}
template <typename T>
operator& (T& data) {
buffer>>data;
}
template <typename T>
operator& (T* data) {
bool found = false;
for (unsigned int i = 0; i < adresses.size() && !found; i++) {
if (data == adresses[i]) {
found = true;
}
}
if (!found) {
data = new T();
adresses.push_back(data);
buffer>>*data;
}
}
template <typename T>
operator& (std::vector<T>& data) {
std::size_t size;
buffer>>size;
for (unsigned int i = 0; i < size; i++)
buffer>>data[i];
}
template <typename T>
operator& (std::vector<T*> data) {
std::size_t size;
buffer>>size;
for (unsigned int i = 0; i < size; i++) {
T* vi = new T();
buffer>>(*vi);
data.push_back(vi);
}
}
template <typename T>
void load (T object, void(T::*func)(ITextArchive&)) {
(object.*func)(*this);
}
template <typename B, typename D>
void load (B* base, void(D::*func)(ITextArchive&)) {
(static_cast<D*>(base)->*func)(*this);
}
template <typename C>
void operator>>(Serializable<C>* object) {
void(C::*func)(ITextArchive&);
object->getSerializeFunc(&func);
load(object, func);
}
private :
std::istream& buffer;
std::vector<unsigned long long int> adresses;
};
}
#endif // ODFAEG_ARCHIVE |
Code:
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
|
#include "myApplication.h"
#include "odfaeg/Graphics/renderWindow.h"
#include "glCheck.h"
#include "odfaeg/Graphics/window.h"
#include <fstream>
#include "odfaeg/Core/archive.h"
#include "odfaeg/Core/serialization.h"
template<typename D>
class BaseObjet : public odfaeg::Serializable<D> {
public :
BaseObjet(std::string name) {
this->name = name;
}
template <typename Archive>
void serialize (Archive & ar) {
ar & name;
}
virtual std::string getName() {
return name;
}
private:
std::string name;
};
class Base : public odfaeg::Serializable<Base> {
public :
Base(std::string name) {
this->name = name;
}
template <typename Archive>
void serialize (Archive & ar) {
ar & name;
}
virtual std::string getName() {
return name;
}
private:
std::string name;
};
class ObjetToutBete : public BaseObjet<ObjetToutBete> {
public:
ObjetToutBete(std::string name) : BaseObjet("Base objet de objet tout bete") {
this->name = name;
}
template <typename Archive>
void serialize (Archive & ar) {
BaseObjet::serialize(ar);
ar & name;
}
std::string getName() {
return BaseObjet::getName() + "\n" + name;
}
private :
std::string name;
};
template <typename D>
class ObjetContenant : public odfaeg::Serializable<ObjetContenant<D>> {
ObjetContenant(std::string name) {
objet = new D(name);
}
template <typename Archive>
void serialize(Archive & ar) {
ar & objet;
}
BaseObjet<D>* objet;
};
int main(int argc, char* args[])
{
std::ofstream ofs("FichierDeSerialisation");
BaseObjet<ObjetToutBete>* ob = new ObjetToutBete("un objet tout bete");
odfaeg::OTextArchive oa(ofs);
oa<<ob;
std::ifstream ifs("FichierDeSerialisation");
odfaeg::ITextArchive ia(ifs);
ia>>ob;
std::cout<<ob->getName()<<std::endl;
} |
Maintenant il ne reste plus qu'à tester ça pour toutes mes classes, voir si j'arrive bien à tout sérialiser.
Si ça marche bah ça aura été tout simple finalement, comparé à l'usine à gaz que m'offre boost.