Sauvegarde binaire d'un objet c++

SAUVEGARDE BINAIRE D'UNE INSTANCE DE CLASSE EN C++ (Pop70)

PRINCIPE

Dans beaucoup de programmes, il peut être utile lors de l'exécution d'enregistrer un objet.
Pour faire ceci, au lieu de sauvegarder chacune des variables, on utilise l'une des méthodes de

fstream : write

.
Son prototype est

ostream& write ( const char* s , streamsize n );

Le premier argument est une chaine de caractère (qui sera l'objet), et le deuxième, la taille de l'objet.

Pour la lecture on utilise la méthode

read : istream& read ( char* s, streamsize n ); 

Pour enregistrer un objet, il suffit alors de faire :

#include <fstream>
ofstream Fichier (chemin, ios ::binary); /* (le paramètre binary, indispensable ici,  peut-être refusé par certain compilateur, toutefois il fonctionne parfaitement sur Code ::Blocks).*/
if ( !Fichier)
{
  return ;
}
write ((char*)&objet, sizeof(objet)) ;
// ...
read((char*) &objet, sizeof(objet));

EXEMPLE

Ici, on va utiliser cette méthode sur une classe Personnage. Les personnages auront un nom (std ::string wNom), un age (unsigned short wAge) et des points de vie (unsigned short wVie).
Il y aura 3 fichiers :
-main.cpp
-Personnage.hpp
-Personnage.cpp

On va commencer par le main.cpp :

#include "Personnage.hpp"
int main()
{
    // CREATION D'UN OBJET PERSONNAGE :
    Personnage *John = new Personnage;

    // LECTURE DE L'OBJET
    std::cout << "Creation d'un personnage : \n";
    std::cout << *John;

    // MODIFICATION DE L'OBJET
    std::string nom = "John Bazq";
    John->SetNom(nom);
    John->SetAge (35);
    John->SetVie (70);

    // LECTURE DE L'OBJET
    std::cout << "Personnage defini : \n";
    std::cout << *John;

    // ENREGISTREMENT DE L'OBJET
    Enregistrer ("JOHN");

    // SUPRESSION DE L'OBJET
    delete John;

    // CREATION DE L'OBJET
    std::cout << "Creation d'un personnage : \n";
    John = new Personnage;

    // LECTURE DE L'OBJET
    std::cout << *John;

    // MODIFICATION A PARTIR DU FICHIER
   Lire ("JOHN");

    // LECTURE DE L'OBJET
    std::cout << "Personnage du fichier : \n";
    std::cout << *John;
    system ("PAUSE");
    return EXIT_SUCCESS;
}

Ensuite on va créer Personnage.hpp, et lui donner toutes les méthodes nécessaires à l'exécution du main.
Il va donc lui falloir les méthodes (publiques) :

    void SetNom(std::string &); 
    std::string GetNom() const;
    void SetAge (unsigned short);
    unsigned short GetAge () const;
    void SetVie (unsigned short);
    unsigned short GetVie () const;
    void Enregistrer (std::string) const;
    void Lire (std::string);
    friend std::ostream& operator<< (std::ostream&, Personnage&);

Plus les constructeurs :

    Personnage();
    ~Personnage() ;

Et les variables membres (privées) énoncées précédemment :

    std::string wNom;
    unsigned short wAge;
    unsigned short wVie;

Il faut aussi penser à protéger le header des inclusions multiples.

On obtient donc :

#ifndef DEF_PERSONNAGE_HPP
#define DEF_PERSONNAGE_HPP
#include 
#include <fstream>

class Personnage
{
public:
    Personnage();
    ~Personnage();

    void SetNom(std::string &);
    std::string GetNom() const;
    void SetAge (unsigned short);
    unsigned short GetAge () const;
    void SetVie (unsigned short);
    unsigned short GetVie () const;
    void Enregistrer (std::string) const;
    void Lire (std::string);
    friend std::ostream& operator<< (std::ostream&, Personnage&);

private:
    std::string wNom;
    unsigned short wAge;
    unsigned short wVie;
};
#endif

Pour finir, on va implémenter cette classe dans Personnage.cpp.
On commence par les méthodes basiques de modifications et d'accessions, en vérifiant les valeurs entrantes et les exceptions.
Le constructeur et le destructeur :

Personnage::Personnage()
{
    wNom = "Aucun_nom";
    wAge = 20;
    wVie = 100;
}
Personnage::~Personnage()
{
}

La méthode de modification du nom :

void Personnage::SetNom(std::string &Nom)
{
    try
    {
        if (Nom == "")
            throw "La chaine de caractere est vide !";
        if (Nom.size() > 40)
            throw "Nom trop long !";
        wNom = Nom;
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

La méthode d'accès du nom :

std::string Personnage::GetNom() const
{
    return wNom;
}

La méthode de modification de l'âge :

void Personnage::SetAge (unsigned short Age)
{
  try
  {
    if (Age > 137)
    throw "L'age est trop grand !";
wAge = Age;
}
catch (const char *exception)
{
std::cerr << "\n*** " << exception << " ***\n";
}
catch (...)
{
std::cerr << "\n*** Une erreur s'est produite ! ***\n";
}
}

La méthode d'accès de l'âge :

unsigned short Personnage::GetAge () const
{
    return wAge;
}

La méthode de modification de la vie :

void Personnage::SetVie (unsigned short Vie)
{
    try
    {
        if (Vie > 100)
            throw "Il y a trop de points de vie !";
        wVie = Vie;
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

La méthode d'accès de la vie :

unsigned short Personnage::GetVie () const
{
    return wVie;
}

Méthode d'enregistrement

Sur le principe : Fichier.write ((char*)&objet, sizeof(objet)) ;

La méthode se trouvant dans la classe devient :
Fichier.write ((char*)this, sizeof(*this)) ;
On peut donc implémenter notre méthode Enregistrer sous cette forme :

void Personnage::Enregistrer (std::string Chemin) const
{
    try
    {
        if (Chemin == "")
            throw "Chemin vide !";
        if (Chemin.size() > 300)
            throw "Chemin trop long";
        std::ofstream fichier_sortant (Chemin.c_str(), std::ios::binary);
        if (!fichier_sortant)
        {
            std::cerr << "\a\n\nImpossible de creer le fichier de sauvegarde\n\n";
            return;
        }
        fichier_sortant.write ((char*) this, sizeof (*this));
        fichier_sortant.close();
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

Lecture du fichier

Sur le même principe : Fichier.read((char*) &objet, sizeof(objet));
devient : Fichier.read((char*) this, sizeof(*this));
La méthode finale :

void Personnage::Lire (std::string Chemin)
{
    try
    {
        if (Chemin == "")
            throw "Chemin vide !";
        if (Chemin.size() > 300)
            throw "Chemin trop long";
        std::ifstream fichier_entrant(Chemin.c_str(), std::ios::binary);
        if (!fichier_entrant)
        {
            std::cerr << "\a\n\nImpossible de lire le fichier de sauvegarde\n\n";
            return;
        }
        fichier_entrant.read((char*) this, sizeof(*this));
        fichier_entrant.close();
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

Sans oublier d'implémenter la surcharge de l'opérateur << :

std::ostream& operator<< (std::ostream &Flux, Personnage &rhs)
{
    try
    {
        Flux << "\nNom du personnage : " << rhs.GetNom()
        << "\nAge du personnage : " << rhs.GetAge()
        << "\nVie restante du personnage : " << rhs.GetVie()
        << "\n\n";
        return Flux;
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

Le code source final :

#include "Personnage.hpp"
Personnage::Personnage()
{
    wNom = "Aucun_nom";
    wAge = 20;
    wVie = 100;
}
Personnage::~Personnage()
{
}
void Personnage::SetNom(std::string &Nom)
{
    try
    {
        if (Nom == "")
            throw "La chaine de caractere est vide !";
        if (Nom.size() > 40)
            throw "Nom trop long !";
        wNom = Nom;
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}
std::string  Personnage::GetNom() const
{
    return wNom;
}
void Personnage::SetAge (unsigned short Age)
{
    try
    {
        if (Age > 137)
            throw "L'age est trop grand !";
        wAge = Age;
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}
unsigned short Personnage::GetAge () const
{
    return wAge;
}
void Personnage::SetVie (unsigned short Vie)
{
    try
    {
        if (Vie > 100)
            throw "Il y a trop de points de vie !";
        wVie = Vie;
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}
unsigned short Personnage::GetVie () const
{
    return wVie;
}
void Personnage::Enregistrer (std::string Chemin) const
{
    try
    {
        if (Chemin == "")
            throw "Chemin vide !";
        if (Chemin.size() > 300)
            throw "Chemin trop long";
        std::ofstream fichier_sortant (Chemin.c_str(), std::ios::binary);
        if (!fichier_sortant)
        {
            std::cerr << "\a\n\nImpossible de creer le fichier de sauvegarde\n\n";
            return;
        }
        fichier_sortant.write ((char*) this, sizeof (*this));
        fichier_sortant.close();
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}
void Personnage::Lire (std::string Chemin)
{
    try
    {
        if (Chemin == "")
            throw "Chemin vide !";
        if (Chemin.size() > 300)
            throw "Chemin trop long";
        std::ifstream fichier_entrant(Chemin.c_str(), std::ios::binary);
        if (!fichier_entrant)
        {
            std::cerr << "\a\n\nImpossible de lire le fichier de sauvegarde\n\n";
            return;
        }
        fichier_entrant.read((char*) this, sizeof(*this));
        fichier_entrant.close();
    }
    catch (const char *exception)
    {
        std::cerr << "\n*** " << exception << " ***\n";
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}
std::ostream& operator<< (std::ostream &Flux, Personnage &rhs)
{
    try
    {
        Flux << "\nNom du personnage : " << rhs.GetNom()
        << "\nAge du personnage : " << rhs.GetAge()
        << "\nVie restante du personnage : " << rhs.GetVie()
        << "\n\n";
        return Flux;
    }
    catch (...)
    {
        std::cerr << "\n*** Une erreur s'est produite ! ***\n";
    }
}

Et voilà ce qu'affiche la console lors de l'exécution :

Ce document intitulé « Sauvegarde binaire d'un objet c++ » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous