Serialisation d'un vecteur

Résolu
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011 - 19 mai 2007 à 12:47
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 - 22 mai 2007 à 13:31
Bonjour,

J'ai un vecteur qui est utilisée pour mémoriser les divers membres du
personnel, donc des instances de la classe MembrePersonnel (qui hérite de personne) et des classes dérivées. La classe résultant est la
classe Personnel.
Cette classe Personnel a pour rôle de faciliter
l'utilisation d'un fichier supporté par un flux et contenant les renseignements
concernant les divers membres du personnel.Mon vecteur contient des pointeurs d'objets, parce que je veux stocker des MembrePersonnel ou autres personnels.

Mes classes :
class Personne
{ ... }

class MembrePersonnel : public Personne
{ .. }

class PersonnelAdmin : public MembrePersonnel
{ ... }

Insertion dans le vecteur :
    ...
    if(type=="MembrePersonnel")
    {
        MembrePersonnel* Mp = new MembrePersonnel;
        Mp->EncodePers();
        Vectpersonne.push_back(Mp);
    }
    if(type=="PersonnelScientifique")
    {
        PersonnelScientifique* Psc=new PersonnelScientifique;
        Psc->EncodePers();
        Vectpersonne.push_back(Psc);
    }
    ...
Maintenant, je veux sauvegarder le vecteur dans un fichier :
class Personnel
{
protected:
    string NomFich;
    vector Vectpersonne;
    ...
    void Personnel::Save_Fich()
    {
        ofstream fichier;
        fichier.open(nom, ios::out);
        for(int i=0;i<(int)Vectpersonne.size();i++)
        {
              fichier<<*Vectpersonne[i];
        }
        fichier.close();
    }
}

Maintenant après la sauvegarde, mon fichier contient seulement les variables de la classe personne et non ceux de PersonnelAdmin.
Comment est ce que je peux obtenir tout les champs en fonction du personnel que je veux ajouter ?

Merci !

29 réponses

luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
19 mai 2007 à 15:00
Le paramètre, c'est simplement "le fichier" dans lequel on veut enregistrer tes données. Qd tu ouvres ton fichier:
ofstream fichier;
fichier.open(nom, ios::out);

C'est bien un object ofstream. C'est cet object qu'on passe par référence (!! Tres important) a ta fonction.
Apparemment, tu ouvres ton ficheir en mode texte, perso, je préfère de loin le mode binaire ( fichier.open(nom, std::ios::out | std::ios::binary) pour les sauvegardes de classe, mais bon, avec le mode texte, voila ce que ca donne dans ton cas.

Admettons que la classe Personne possède les champs: std::string nom, std::string prenom, int age, std::string addresse;
La classe MembrePersonnel : std::string motdepasse, int identificateur, float salaire;
(les membres sont pris au hasard ^^)

La fonction dans la classe Personne:
virtual void Personne::WriteToFile(std::ofstream & _file)   ( note: le mot clef virtual n'apparait pas dans le .cpp)
{
_file << nom << " " << prenom << std::endl;<= le std::endl c'est pour passer a la ligne (notamment)
_file << age << std::endl;
_file << adresse << std::endl;
}

Dans la classe MembrePersonnel :
virtual void MembrePersonnel ::WriteToFile(std::ofstream & _file)
{
Personne::WriteToFile(_file); < = appel de la classe Personne

_file << motdepasse << std::endl;
_file << identificateur << std::endl;
_file << salaire << std::endl;
}

Dans la classe PersonnelAdmin:
virtual void PersonnelAdmin:::WriteToFile(std::ofstream & _file)

{
MembrePersonnel ::WriteToFile(_file); <= appel de la classe MembrePersonnel

etc...

}

J'vais pas fait attention dans mon premier post, ya peut etre quelques erreurs, je pensais que PersonnelAdmin et MembrePersonnel dérivait de Personne, alors que PersonnelAdmin dérive de MembrePersonnel.
3
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
19 mai 2007 à 19:28
Pour désérialiser, ca sera une fonction de ce type:

virtual CLASS::ReadFromFile(std::ifstream & _inFile)
{
   BASE_CLASS::ReadFromFile(_inFile);

   _inFile >> ...
   ...
}

Tu as juste a lire dans le meme sens que celui que tu as utilisé pour l'écriture.
L'opérateur >> de ifstream retourne un booléen, donc tu peux vérifier que le fichier est correct de cette facon:

  if (!(_inFile >> variable_de_type_qcq_géré_par_istream))
{
ERREUR de conversion.
}
else
tout va bien

Et pour la doc:
http://www.cplusplus.com/reference/iostream/
3
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 23:54
Et tu as bien mis :
Personne * Personne
::BuildObjectFromFile(std::ifstream & _inFile) {...} dans le .cpp ?
3
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 mai 2007 à 12:07
Ah je sais ... La, c 'est a toi d'essayer de voir ce qu'il se passe.
La fonction BuildObjectFromFile va lire la première ligne pour extraire le type de l'object qu'elle s'apprete a lire. Apres avoir extrait le type, faut que le pointeur du fichier se remette au début de la personne, cad:

Personne * Personne::BuildObjectFromFile(std::ifstream & _file)
{
    std::string type;
    int position = _file.tellg();    <= on mémorise là ou on se trouve avant d'extraire la ligne.
    _file >> type;

    _file.seekg(position);       <= on se replace au début de la personne pour que la fonction ReadFromFile lise bien la premire ligne.

    if  (type == std::string("..."))
    ...
    else return NULL;
}

Ca, c'est la solution que je te conseille, l'autre c'est de simplement zapper la lecture du type dans la fonction ReadFromFile, mais c'est vraiment pas terrible, car du coup un object ne peut plus se lire correctement lui meme. Puisqu'il lira jamais la première ligne et donc sera toujours décallé.

Compris ?
3

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
22 mai 2007 à 13:08
Non je comprends pas, car peut importe ou se trouve ton object, il est en mémoire. Faut pas se dire que le "vector" c'est un truc magique et particulier. C'est juste un bloc en mémoire dans lequel sont juxtaposés tes objects. Mais post ici ton code entier, ou les fonctions de chargement et autre. Si qd tu fais Vectpersonne[i]->AffichePers() et que ca affiche pas des valeurs corrects, c'est que le fichier n'a pas été lue correctement.

Donc le vecteur n'a aucune influence, surtout que c'est un vecteur de pointeur. (pas le choix ici). Regarde avec ton débuggeur aussi pour lire le contenu de ton vecteur, cad la valeur des pointeurs et tout.

Il faut aussi faire des tests: par exemple, a chaque membre lue du fichier, ajoute un std::cout pour voir la valeur lue:
_inFile >> nom;
std::cout << nom << std::endl;

Et cela partout, il faut déterminer précisément PK et OU il y a un pb. Mais je le répète le plus simple deja, c'est que tu postes ton code ici. :)
3
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
19 mai 2007 à 13:54
Déclares une fonction "virtual" dans ta classe Personne:




class Personne
{
public:

virtual void WriteToFile(std::ofstream & _file)
{
... <= la tu enregistres tous les membres de personne
}
}

Tu fais de meme pour chaque classe dérivée:

class MembrePersonnel
{
public:
virtual void WriteToFile(std::ofstream & _file) < = comme elle est virtual, c'est cette fonction qui va etre appelé
{
Personne::WriteToFile(_file); <= la on appelle la fonction qui va enregistrer les membres de personnes

... <= puis tu ajoutes les membres de "MembrePersonnel"
}
}

Donc le principe c'est:
Comme la fonction est virtual, c'est la fonction de la classe dérivée qui va etre appellé:
Personne * unePersonne = ...;
unePersonne->WriteToFile(...) <= ce n'est PAS la fonction WriteToFile de Personne qui est appelé, mais celle de la classe dérivée si elle contient une fonction WriteToFile.




Donc au final, qd tu veux enregistrer toutes les peronnes:



"Maintenant, je veux sauvegarder le vecteur dans un fichier :

class Personnel
{
protected:
    string NomFich;
    vector Vectpersonne;
    ...
    void Personnel::Save_Fich()
    {
        ofstream fichier;
        fichier.open(nom, ios::out);
        for(int i =0;i<(int)Vectpersonne.size();i++)
        {
              Vectpersonne[i]->WriteToFile(fichier);
        }
        fichier.close();
    }
}"
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
19 mai 2007 à 14:04
J'avais oublie cette notion de Virtual, merci !
Mais je comprend pas bien ton parametre (std::ofstream & _file) pour enregistrer tous les membres de personne par exemple. Pourrais tu me donner un exemple pour enregistrer un champ ?

Merci!
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
19 mai 2007 à 17:44
Ok merci bcp pour ton aide !

a+
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
19 mai 2007 à 19:19
Et si je veux désérialiser, il faut que je fasse une fonction virtual pour ecrire dans le vecteur ?
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 15:33
Je n'arrive toujour pas a charger les donnees du fichier dans le vecteur.

void Personnel::Charge_Fich()
{
    Personne tmp;
    int taille=(int)NomFich.size(),i;
    char* nom=new char[taille];
    for(i=0;i<taille && NomFich[i] != '\0';i++)
        nom[i]=NomFich[i];
    nom[i]='\0';

    ifstream FichierInput;
    FichierInput.open(nom,ios::in);
  
     //ICI : il me manque l instruction du ReadFromFile(FichierInput)
  
    Vectpersonne.push_back(tmp);
    FichierInput.close();
}

Qlq pourrait m 'aider ?
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 20:28
Ah oui, au temps pour moi, j'ai oublié un détail. Il faut en fait que ce soit la classe Personne qui s'occupe de gérer le chargement car on ne sait pas a priori quel type de personne est sauvegardé. Donc il faut que tu enregistres en meme temps le type de personne. Donc ce que j'ai dis dans le premier post doit etre un peu modifié.

Ce qui se fait en pratique, c'est que l'on construit des "class factory" mais bon dans ton cas, c'est pas nécessaire. Un truc codé brutalement suffit.

Donc apres, je te fais un exemple "sale" dans le sens où on peut faire des trucs mieux structurés, mais c'est le principe :

La fonction suivante retourne un object extrait du fichier. Cet object dépend de ce que l'on va trouver dans le fichier. Donc tu peux par exemple déclarer une fonction statique dans ta classe qui retourne l'objet lue dans le fichier.

static Personne *  Personne::BuildObjectFromFile(ifstream & _inFile)
{
std::string class_type;
_inFile >> class_type; <= la première chose enregistré est le nom de la classe ou n'importe quelle identificateur pouvant déterminer de facon précise la class concerné par l'object.

if (class_type == std::string("MembrePersonnel"))
{
    MembrePersonnel * newPersonnel = new MembrePersonnel();

    newPersonnel-> Load (_inFile);
    return static_cast(newPersonnel);  < = conversion implicite par le compilo, autant le préciser explicitement.
}
else if (class_type = = std::string("PersonnelAdmin"))
{
    PersonnelAdmin * newPerson = new PersonnelAdmin();

    newPerson-> Load (_inFile);
    return static_cast(newPerson);
}
else if (class_type == ...)
else return NULL; <= object non reconnu
}

Donc en fait dans ton fichier, si tu imagines que tu as sauvé un object de type PersonnelAdmin, tu auras

PersonnelAdmin
Membre 1 de personne
...
Membre n de personne
Membre 1 de MembrePersonnel
...
Membre n de MembrePersonnel
Membre 1 de PersonnelAdmin
...
Membre n de PersonnelAdmin

Donc une classe comportera les fonctions:
class PersonnelAdmin : public MembrePersonnel
{
protected:
void WriteToFile(std::ofstream & _file); < = Membre qui ne doit pas etre appelé de l'extérieur
{
  MembrePersonnel::WriteToFile(_file);

 _file << Members ....
}

void ReadFromFile(std::ifstream & _file)
{
  MembrePersonnel::ReadFromFile(_file);

  _file >> members ...
}

public:
virtual void Save(std::ofstream & _outFile)
{
  _file << std::string("PersonnelAdmin") << std::endl; <= on écrit juste le nom de la classe

  PersonnelAdmin::WriteToFile(_outFile); <= Le nom de la classe devant est facultatif, mais c'est pour que ce soit bien clair. Autant le mettre.
}

virtual void Load(std::ifstream & _inFile)
{
  std::string type;
  _inFile >> type;
 
   assert(type == std::string("PersonnelAdmin")); // erreur si le type est pas correct
   PersonnelAdmin::ReadFromFile(_inFile);
}
};

Pour la class  MembrePersonnel, on retrouve la meme chose:
class Membrepersonnel
{
protected:
void WriteToFile(..):
void ReadFromFile(..);

public:
virtual void Save(..);
virtual void Load(..):
};

La fonction Save et Load de chaque classe peut etre déclarer virtuelle pure: virtual void Save() = 0; ce qui signifie que chaque classe dérivé a pour OBLIGATION de redéfinir une fonction Save sauf si elle est elle meme une classe abstraite (cad qui possède une fonction virtuelle pure). Si tu tentes de déclarer un object d'une classe abstraite, la compilation échouera.

Donc hésite pas si t'as des questions, ce que j'ai écris, c'est fait en vitesse, ca doit etre truffer de petites erreurs, mais c'est le principe qui est important. Mais encore une fois, c'est juste l'idée de base. Pour des gros projets, on fait des trucs plus élaborés notamment au niveau de la fonction Personne::BuildObjectFromFile comme tu peux t'en douter.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 20:37
Oue j'oubliais donc dernier détail, pour lire ton fichier, ca devient:

void Personnel::Charge_Fich()
{
    int taille=(int)NomFich.size(),i;
    char* nom=new char[taille];
    for(i=0;i<taille && NomFich[i] != '\0';i++)
        nom[i]=NomFich[i];
    nom[i]='\0';

    ifstream FichierInput;
    FichierInput.open(nom,ios::in);

    Personne * newPersonne = NULL;
  
    while ( (newPersonne = Personne::BuildObjectFromFile(FichierInput) ) != NULL)
        Vectpersonne.push_back(newPersonne);

// La boucle s'arrete si toutes les personnes ont été lues, ou si une erreur a été détecté.

    FichierInput.close();
}
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 22:23
Encore un grand merci !

Mais je ne comprend pas bien l'utilité des fonctions Load et Save. C'est pour obtenir le type d'objet, donc PersonnelAdmin,MembrePersonnel,... ?
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 22:30
Les fonctions WriteToFile et LoadFromFile sont utilisés par toute la hiérarchie dont dérivé une classe. Or il faut que le type soit uniquement marqué une fois et par la classe le plus bas de la hiérarchie de l'object. Donc le caractère "virtual" de Load et Save fait que le programme va aller chercher la fonction de la classe la plus dérivée de l'object et écrire le type. Alors que WriteToFile et LoadFromFile ne font qu'écrire les membres.

En fait, on pourrait faire un peu autrement:
virtual void Personne::WriteToFile(..)
{
file << "Personne" << std::endl;
file << members ...
}

virtual void MembrePersonnel::WriteToFile(..)
{
file << "MembrePersonnel" << std::endl;
Personne::WriteToFile(..);
file << Members ...
}

virtual void PersonnelAdmin::WriteToFile(..)
{
file << "PersonnelAdmin" << std::endl;
Personne::WriteToFile(..)
file << Members ...
}

Dans un fichier ou l'on enregistre un object PersonnelAdmin on aurait donc
PersonnelAdmin
MembrePersonnel
Personne
Membre de Personne 1
...
Membre de Personne n
Membre de MembrePersonnel 1
...
Membre de MembrePersonnel n
Membre de PersonnelAdmin 1
...
Membre de PersonnelAdmin n

Le ReadFromFile serait donc rigoureusement l'inverse. Cette solution est pas mal, puisque plus besoin de fonction Load/Save. C'est selon tes gouts finalement.
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 22:33
En fait , j'avais déja inseré une variable type dans la classe personne. Ce qui revient a la même chose que tu viens écrire. Je vais plutôt essayer cela au lieu des fonctions Load/Save.

Merci
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 23:14
Maintenant ,lorsque je fais le changement dans la fonction Charge_Fich() il me met : fatal error LNK1120 :1 externe non resolus

Sais-tu ce qui ne va pas ?
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 23:25
Ca ne te le faisait avant ? Si non, tu as surement oublié de déclarer une fonction.

Post plus de code, pas facile sans support. Charge_Fich() c'est la fonction statique ?
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 23:27
Ca sera la fonction qui se charge au moment du demarage du programe de charger tout dans le vecteur :

void Personnel::Charge_Fich()
{
    int Last;
    Personne tmp;
    int taille=(int)NomFich.size(),i;
    char* nom=new char[taille];
    for(i=0;i<taille && NomFich[i] != '\0';i++)
        nom[i]=NomFich[i];
    nom[i]='\0';

    ifstream FichierInput;
    FichierInput.open(nom,ios::in);
    //Personne * newPersonne = NULL;

    //while ( (newPersonne = Personne::BuildObjectFromFile(FichierInput) ) != NULL)
    //    Vectpersonne.push_back(newPersonne);
    FichierInput.close();
}

Lorsque je mets les lignes en commentaire,il n'y a plus d'erreur fatale.
0
luhtor Messages postés 2023 Date d'inscription mardi 24 septembre 2002 Statut Membre Dernière intervention 28 juillet 2008 6
21 mai 2007 à 23:37
Comment as tu déclarer BuildObjectFromFile ? Qu'as tu mis dans le .cpp, dans le .h etc ...
0
Spawn3107 Messages postés 84 Date d'inscription mardi 14 décembre 2004 Statut Membre Dernière intervention 28 mars 2011
21 mai 2007 à 23:39
Dans Personne.h :
...
Public:
static Personne * BuildObjectFromFile(ifstream & _inFile);
...

Dans Personne.cpp :

static Personne * BuildObjectFromFile(ifstream & _inFile)
{
        string class_type;
        _inFile >> class_type; // la première chose enregistré est le nom de la classe

        if (class_type == string("MembrePersonnel"))
        {
            MembrePersonnel * newPersonnel = new MembrePersonnel();
            newPersonnel->Load(_inFile);
           
            return static_cast(newPersonnel);  // conversion implicite par le compilo, autant le préciser explicitement.
        }
        else if (class_type == std::string("PersonnelAdmin"))
        {
            PersonnelAdmin * newPerson = new PersonnelAdmin();
            newPerson->Load(_inFile);
            return static_cast(newPerson);
        }
        else if (class_type == string("PersonnelScientifique"))
        ...
        else return NULL; //object non reconnu
}
0
Rejoignez-nous