15.2. Les tampons

Les classes de gestion des tampons de la bibliothèque standard C++ se situent au coeur des opérations d'écriture et de lecture sur les flux de données physiques qu'un programme est susceptible de manipuler. Bien qu'elles ne soient quasiment jamais utilisées directement par les programmeurs, c'est sur ces classes que les classes de flux s'appuient pour effectuer les opérations d'entrée sortie. Il est donc nécessaire de connaître un peu leur mode de fonctionnement.

15.2.1. Généralités sur les tampons

Un tampon, également appelé cache, est une zone mémoire dans laquelle les opérations d'écriture et de lecture se font et dont le contenu est mis en correspondance avec les données d'un média physique sous-jacent. Les mécanismes de cache ont essentiellement pour but d'optimiser les performances des opérations d'entrée / sortie. En effet, l'accès à la mémoire cache est généralement beaucoup plus rapide que l'accès direct au support physique ou au média de communication. Les opérations effectuées par le programme se font donc, la plupart du temps, uniquement au niveau du tampon, et ce n'est que dans certaines conditions que les données du tampon sont effectivement transmises au média physique. Le gain en performance peut intervenir à plusieurs niveau. Les cas les plus simples étant simplement lorsqu'une donnée écrite est écrasée peu de temps après par une autre valeur (la première opération d'écriture n'est alors jamais transmise au média) ou lorsqu'une donnée est lue plusieurs fois (la même donnée est renvoyée à chaque lecture). Bien entendu, cela suppose que les données stockées dans le tampon soient cohérentes avec les données du média, surtout si les données sont accédées au travers de plusieurs tampons. Tout mécanisme de gestion de cache permet donc de vider les caches (c'est-à-dire de forcer les opérations d'écriture) et de les invalider (c'est-à-dire de leur signaler que leurs données sont obsolètes et qu'une lecture physique doit être faite si on cherche à y accéder).

Les mécanismes de mémoire cache et de tampon sont très souvent utilisés en informatique, à tous les niveaux. On trouve des mémoires cache dans les processeurs, les contrôleurs de disque, les graveurs de CD, les pilotes de périphériques des systèmes d'exploitation et bien entendu dans les programmes. Chacun de ces caches contribue à l'amélioration des performances globales en retardant au maximum la réalisation des opérations lentes et en optimisant les opérations de lecture et d'écriture (souvent en les effectuant en groupe, ce qui permet de réduire les frais de communication ou d'initialisation des périphériques). Il n'est donc absolument pas surprenant que la bibliothèque standard C++ utilise elle aussi la notion de tampon dans toutes ses classes d'entrée / sortie...

15.2.2. La classe basic_streambuf

Les mécanismes de base des tampons de la bibliothèque standard sont implémentés dans la classe template basic_streambuf. Cette classe n'est pas destinée à être utilisée telle quelle car elle ne sait pas communiquer avec les supports physiques des données. En fait, elle ne peut être utilisée qu'en tant que classe de base de classes plus spécialisées, qui elles fournissent les fonctionnalités d'accès aux médias par l'intermédiaire de fonctions virtuelles. La classe basic_streambuf appelle donc ces méthodes en diverses circonstances au sein des traitements effectués par son propre code de gestion du tampon, aussi bien pour signaler les changements d'état de celui-ci que pour demander l'écriture ou la lecture des données dans la séquence sous contrôle.

La classe basic_streambuf fournit donc une interface publique permettant d'accéder aux données du tampon d'un côté et définit l'interface de communication avec ses classes filles par l'intermédiaire de ses méthodes virtuelles de l'autre coté. Bien entendu, ces méthodes virtuelles sont toutes déclarées en zone protégée afin d'éviter que l'on puisse les appeler directement, tout en permettant aux classes dérivées de les redéfinir et d'y accéder.

En interne, la classe basic_streambuf encapsule deux tampons, un pour les écritures et un pour les lectures. Cependant, ces tampons accèdent à la même mémoire et à la même séquence de données physiques. Ces deux tampons peuvent être utilisés simultanément ou non, suivant la nature de la séquence sous contrôle et suivant le flux qui utilise le tampon. Par exemple, les flux de sortie n'utilisent que le tampon en écriture, et les flux d'entrée que le tampon en lecture.

La classe basic_streambuf gère ses tampons d'entrée et de sortie à l'aide d'une zone de mémoire interne qui contient un sous-ensemble des données de la séquence sous contrôle. Les deux tampons travaillent de manière indépendante sur cette zone de mémoire et sont chacun représentés à l'aide de trois pointeurs. Ces pointeurs contiennent respectivement l'adresse du début de la zone mémoire du tampon, son adresse de fin et l'adresse de la position courante en lecture ou en écriture. Ces pointeurs sont complètement gérés en interne par la classe basic_streambuf, mais les classes dérivées peuvent y accéder et les modifier en fonction de leurs besoins par l'intermédiaire d'accesseurs. Les pointeurs d'un tampon peuvent parfaitement être nuls si celui-ci n'est pas utilisé. Toutefois, si le pointeur référençant la position courante n'est pas nul, ses pointeurs associés ne doivent pas l'être et la position courante référencée doit obligatoirement se situer dans une zone mémoire définie par les pointeurs de début et de fin du tampon.

La classe basic_streambuf est déclarée comme suit dans l'en-tête streambuf :

template <class charT, class traits =
    char_traits<charT> >
class basic_streambuf
{
public:
// Les types de base :
    typedef charT                     char_type;
    typedef typename traits::int_type int_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;
    typedef traits                    traits_type;

    // Les méthodes publiques utilisables par les classes de flux :

// Les méthodes de gestion des locales :
    locale pubimbue(const locale &loc);
    locale getloc() const;

// Les méthodes de gestion du tampon :
    basic_streambuf<char_type,traits> *
        pubsetbuf(char_type* s, streamsize n);
    pos_type pubseekoff(off_type off, ios_base::seekdir sens,
        ios_base::openmode mode = ios_base::in | ios_base::out);
    pos_type pubseekpos(pos_type sp,
        ios_base::openmode mode = ios_base::in | ios_base::out);
    int pubsync();

// Méthodes d'accès au tampon en lecture :
    streamsize in_avail();
    int_type sgetc();
    int_type sbumpc();
    int_type snextc();
    streamsize sgetn(char_type *s, streamsize n);

// Méthode d'annulation de lecture d'un caractère :
    int_type sputbackc(char_type c);
    int_type sungetc();

// Méthode d'accès en écriture :
    int_type   sputc(char_type c);
    streamsize sputn(const char_type *s, streamsize n);

    // Le destructeur :
    virtual ~basic_streambuf();

protected:
    // Les méthodes protected utilisables par
    // les classes dérivées :

// Le constructeur :
    basic_streambuf();

// Méthodes d'accès aux pointeurs du tampon de lecture :
    char_type *eback() const;
    char_type *gptr()  const;
    char_type *egptr() const;
    void      gbump(int n);
    void      setg(char_type *debut, char_type *suivant,
        char_type *fin);

// Méthodes d'accès aux pointeurs du tampon d'écriture :
    char_type *pbase() const;
    char_type *pptr() const;
    char_type *epptr() const;
    void      pbump(int n);
    void      setp(char_type *debut, char_type *fin);

    // Les méthodes protected virtuelles, que les classes
    // dérivées doivent implémenter :

    virtual void imbue(const locale &loc);
    virtual basic_streambuf<char_type, traits>*
        setbuf(char_type *s, streamsize n);
    virtual pos_type seekoff(off_type off, ios_base::seekdir sens,
        ios_base::openmode mode = ios_base::in | ios_base::out);
    virtual pos_type seekpos(pos_type sp,
        ios_base::openmode mode = ios_base::in | ios_base::out);
    virtual int        sync();
    virtual int        showmanyc();
    virtual streamsize xsgetn(char_type *s, streamsize n);
    virtual int_type   underflow();
    virtual int_type   uflow();
    virtual int_type   pbackfail(int_type c = traits::eof());
    virtual streamsize xsputn(const char_type* s, streamsize n);
    virtual int_type   overflow (int_type c = traits::eof());
};

Comme vous pouvez le constater, le constructeur de la classe basic_streambuf est déclaré en zone protected, ce qui empêche quiconque de l'instancier. C'est normal, puisque cette classe n'est destinée à être utilisée qu'en tant que classe de base d'une classe spécialisée pour un média spécifique. En revanche, les méthodes virtuelles ne sont pas pures, car elles fournissent un comportement par défaut qui conviendra dans la plupart des cas.

L'interface publique comprend des méthodes d'ordre générale et des méthodes permettant d'effectuer les opérations d'écriture et de lecture sur les tampons encapsulés par la classe basic_streambuf. Pour les distinguer des méthodes virtuelles qui doivent être implémentées dans les classes dérivées, leur nom est préfixé par pub (pour « publique »).

Les méthodes pubimbue et locale permettent respectivement de fixer la locale utilisée par le programme pour ce tampon et de récupérer la locale courante. Par défaut, la locale globale active au moment de la construction du tampon est utilisée. Les notions de locales seront décrites dans le Chapitre 16.

Les méthodes pubsetbuf, pubseekoff et pubseekpos permettent quant à elles de paramétrer le tampon d'entrée / sortie. Ces méthodes se contentent d'appeler les méthodes virtuelles setbuf, seekoff et seekpos, dont le comportement, spécifique à chaque classe dérivée, sera décrit ci-dessous.

Viennent ensuite les méthodes d'accès aux données en lecture et en écriture. Les méthodes de lecture sont respectivement les méthodes sgetc, sbumpc et snextc. La méthode sgetc permet de lire la valeur du caractère référencé par le pointeur courant du tampon d'entrée. Cette fonction renvoie la même valeur à chaque appel, car elle ne modifie pas la valeur de ce pointeur. En revanche, la méthode sbumpc fait avancer ce pointeur après la lecture du caractère courant, ce qui fait qu'elle peut être utilisée pour lire tous les caractères du flux de données physiques. Enfin, la méthode snextc appelle la méthode sbumpc dans un premier temps, puis renvoie la valeur retournée par sgetc. Cette méthode permet donc de lire la valeur du caractère suivant dans le tampon et de positionner le pointeur sur ce caractère. Notez que, contrairement à la méthode sbumpc, snextc modifie la valeur du pointeur avant de lire le caractère courant, ce qui fait qu'en réalité elle lit le caractère suivant. Toutes ces méthodes renvoient la valeur de fin de fichier définie dans la classe des traits du type de caractère utilisé en cas d'erreur. Elles sont également susceptibles de demander la lecture de données complémentaires dans le cadre de la gestion du tampon.

La méthode in_avail renvoie le nombre de caractères encore stockés dans le tampon géré par la classe basic_streambuf. Si ce tampon est vide, elle renvoie une estimation du nombre de caractères qui peuvent être lus dans la séquence contrôlée par le tampon. Cette estimation est un minimum, la valeur renvoyée garantit qu'autant d'appel à sbumpc réussiront.

Les tampons de la bibliothèque standard donnent la possibilité aux programmes qui les utilisent d'annuler la lecture d'un caractère. Normalement, ce genre d'annulation ne peut être effectué qu'une seule fois et la valeur qui doit être replacée dans le tampon doit être exactement celle qui avait été lue. Les méthodes qui permettent d'effectuer ce type d'opération sont les méthodes sputbackc et sungetc. La première méthode prend en paramètre la valeur du caractère qui doit être replacé dans le tampon et la deuxième ne fait que décrémenter le pointeur référençant l'élément courant dans le tampon de lecture. Ces deux opérations peuvent échouer si la valeur à replacer n'est pas égale à la valeur du caractère qui se trouve dans le tampon ou si, tout simplement, il est impossible de revenir en arrière (par exemple parce qu'on se trouve en début de séquence). Dans ce cas, ces méthodes renvoient la valeur de fin de fichier définie dans la classe des traits du type de caractère utilisé, à savoir traits::eof().

Enfin, les méthodes d'écriture de la classe basic_streambuf sont les méthodes sputc et sputn. La première permet d'écrire un caractère unique dans le tampon de la séquence de sortie et la deuxième d'écrire toute une série de caractères. Dans ce dernier cas, les caractères à écrire sont spécifiés à l'aide d'un tableau dont la longueur est passée en deuxième paramètre à sputn. Ces deux méthodes peuvent renvoyer le caractère de fin de fichier de la classe des traits du type de caractère utilisé pour signaler une erreur d'écriture.

L'interface protégée de la classe basic_streambuf est constituée des accesseurs aux pointeurs sur les tampons d'entrée et de sortie d'une part et, d'autre part, des méthodes virtuelles que les classes dérivées doivent redéfinir pour implémenter la gestion des entrées / sorties physiques.

Les pointeurs du tableau contenant les données du tampon de lecture peuvent être récupérés par les classes dérivées à l'aide des méthodes eback, gptr et egptr. La méthode eback renvoie le pointeur sur le début du tableau du tampon d'entrée. Les méthodes gptr et egptr renvoient quant à elles le pointeur sur l'élément courant, dont la valeur peut être obtenue avec la méthode sgetc, et le pointeur sur la fin du tableau du tampon. Le nom de la méthode gptr provient de l'abréviation de l'anglais « get pointer » et celui de la méthode egptr de l'abréviation « end of gptr ». Enfin, les méthodes gbump et setg permettent respectivement de faire avancer le pointeur sur l'élément courant d'un certain nombre de positions et de fixer les trois pointeurs du tampon de lecture en une seule opération.

Les pointeurs du tampon d'écriture sont accessibles par des méthodes similaires à celles définies pour le tampon de lecture. Ainsi, les méthodes pbase, pptr et epptr permettent respectivement d'accéder au début du tableau contenant les données du tampon d'écriture, à la position courante pour les écritures et au pointeur de fin de ce tableau. « pptr » est ici l'abréviation de l'anglais « put pointer ». Les méthodes pbump et setp jouent le même rôle pour les pointeurs du tampon d'écriture que les méthodes gbump et setg pour les pointeurs du tampon de lecture.

Enfin, les méthodes protégées de la classe basic_streambuf permettent, comme on l'a déjà indiqué ci-dessus, de communiquer avec les classes dérivées implémentant les entrées / sorties physiques. Le rôle de ces méthodes est décrit dans le tableau ci-dessous :

MéthodeDescription
imbue

Cette méthode est appelée à chaque fois qu'il y a un changement de locale au niveau du tampon. Les classes dérivées de la classe basic_streambuf sont assurées qu'il n'y aura pas de changement de locale entre chaque appel à cette fonction et peuvent donc maintenir une référence sur la locale courante en permanence. Les notions concernant les locales seront décrites dans le Chapitre 16.

setbuf

Cette méthode n'est appelée que par la méthode pubsetbuf. Elle a principalement pour but de fixer la zone mémoire utilisée par le tampon. Ceci peut ne pas avoir de sens pour certains médias, aussi cette méthode peut-elle ne rien faire du tout. En pratique, si cette fonction est appellée avec deux paramètres nuls, les mécanismes de gestion du cache doivent être désactivés. Pour cela, la classe dérivée doit fixer les pointeurs des tampons de lecture et d'écriture à la valeur nulle.

seekoff

Cette méthode n'est appelée que par la méthode pubseekoff. Tout comme la méthode setbuf, sa sémantique est spécifique à chaque classe dérivée de gestion des médias physiques. En général, cette fonction permet de déplacer la position courante dans la séquence de données d'un certain décalage. Ce décalage peut être spécifié relativement à la position courante, au début ou à la fin de la séquence de données sous contrôle. Le mode de déplacement est spécifié à l'aide du paramètre sens, qui doit prendre l'une des constantes de type seekdir définie dans la classe ios_base. De même, le tampon concerné par ce déplacement est spécifié par le paramètre mode, dont la valeur doit être l'une des constante de type ios_base::openmode. Ces types et ces constantes seront décrits avec la classe de base ios_base dans la Section 15.3.

seekpos

Cette méthode n'est appelée que par la méthode pubseekpos. Elle fonctionne de manière similaire à la méthode seekoff puisqu'elle permet de positionner le pointeur courant des tampons de la classe basic_streambuf à un emplacement arbitraire dans la séquence de données sous contrôle.

sync

Cette méthode n'est appelée que par la méthode pubsync et permet de demander la synchronisation du tampon avec la séquence de données physiques. Autrement dit, les opérations d'écritures doivent être effectuées sur le champ afin de s'assurer que les modifiations effectuées dans le cache soient bien enregistrées.

showmanyc

Cette méthode est appelée par la méthode in_avail lorsque la fin du tampon de lecture a été atteinte. Elle doit renvoyer une estimation basse du nombre de caractères qui peuvent encore être lus dans la séquence sous contrôle. Cette estimation doit être sûre, dans le sens où le nombre de caractères renvoyés doit effectivement pouvoir être lu sans erreur.

xsgetn

Cette méthode n'est appelée que par la méthode sgetn. Elle permet d'effectuer la lecture de plusieurs caractères et de les stocker dans le tableau reçu en paramètre. La lecture de chaque caractère doit se faire exactement comme si la méthode sbumpc était appelée successivement pour chacun d'eux afin de maintenir le tampon de lecture dans un état cohérent. La valeur retournée est le nombre de caractères effectivement lus ou traits::eof() en cas d'erreur.

underflow

Cette méthode est appelée lorsque la fin du tampon est atteinte lors de la lecture d'un caractère. Cela peut se produire lorsqu'il n'y a plus de caractère disponible dans le tampon ou tout simplement à chaque lecture, lorsque le mécanisme de cache est désactivé. Cette fonction doit renvoyer le caractère suivant de la séquence sous contrôle et remplir le tampon si nécessaire. Le pointeur référençant le caractère courant est alors initialisé sur le caractère dont la valeur a été récupérée. Ainsi, la méthode underflow doit remplir le tampon, mais ne doit pas faire avancer la position courante de lecture. Cette méthode peut renvoyer traits::eof() en cas d'échec, ce qui se produit généralement lorsque la fin de la séquence sous contrôle a été atteinte.

uflow

Cette méthode est appelée dans les mêmes conditions que la méthode underflow. Elle doit également remplir le tampon, mais, contrairement à underflow, elle fait également avancer le pointeur du caractère courant d'une position. Ceci implique que cette méthode ne peut pas être utilisée avec les flux non bufferisés. En général, cette méthode n'a pas à être redéfinie parce que le code de la méthode uflow de la classe basic_streambuf effectue ces opérations en s'appuyant sur la méthode underflow. Cette méthode peut renvoyer traits::eof() en cas d'échec.

pbackfail

Cette méthode est appelée lorsque la méthode sputbackc échoue, soit parce que le pointeur de lecture se trouve au début du tampon de lecture, soit parce que le caractère qui doit être replacé dans la séquence n'est pas le caractère qui vient d'en être extrait. Cette méthode doit prendre en charge le déplacement des caractères du tampon pour permettre le replacement du caractère fourni en paramètre et mettre à jour les pointeurs de gestion du tampon de lecture en conséquence. Elle peut renvoyer la valeur traits::eof() pour signaler un échec.

xsputn

Cette méthode n'est appelée que par la méthode sputn. Elle permet de réaliser l'écriture de plusieurs caractères dans la séquence de sortie. Ces caractères sont spécifiés dans le tableau fourni en paramètre. Ces écritures sont réalisées exactement comme si la méthode sputc était appelée successivement pour chaque caractère afin de maintenir le tampon d'écriture dans un état cohérent. La valeur retournée est le nombre de caractères écrits ou traits::eof() en cas d'erreur.

overflow

Cette méthode est appelée par les méthodes d'écriture de la classe basic_streambuf lorsque le tampon d'écriture est plein. Elle a pour but de dégager de la place dans ce tampon en consommant une partie des caractères situés entre le pointeur de début du tampon et le pointeur de position d'écriture courante. Elle est donc susceptible d'effectuer les écritures physiques sur le média de sortie au cours de cette opération. Si l'écriture réussit, les pointeurs de gestion du tampon d'écriture doivent être mis à jour en conséquence. Dans le cas contraire, la fonction peut renvoyer la valeur traits::eof() pour signaler l'erreur ou lancer une exception.

15.2.3. Les classes de tampons basic_stringbuf et basic_filebuf

Vous l'aurez compris, l'écriture d'une classe dérivée de la classe basic_streambuf prenant en charge un média peut être relativement technique et difficile. Heureusement, cette situation ne se présente quasiment jamais, parce que la bibliothèque standard C++ fournit des classes dérivées prenant en charge les deux situations les plus importantes : les tampons d'accès à une chaîne de caractères et les tampons d'accès aux fichiers. Ces classes sont respectivement les classes template basic_stringbuf et basic_filebuf.

15.2.3.1. La classe basic_stringbuf

La classe basic_stringbuf permet d'effectuer des entrées / sorties en mémoire de la même manière que si elles étaient effectuées sur un périphérique d'entrée / sortie normal. Le but de cette classe n'est évidemment pas d'optimiser les performances à l'aide d'un cache puisque les opérations se font à destination de la mémoire, mais d'uniformiser et de permettre les mêmes opérations de formatage dans des chaînes de caractères que celles que l'on peut réaliser avec les flux d'entrée / sortie normaux.

La classe basic_streambuf dérive bien entendu de la classe basic_streambuf puisqu'elle définit les opérations fondamentales d'écriture et de lecture dans une chaîne de caractères. Elle est déclarée comme suit dans l'en-tête sstream :

template <class charT,
    class traits = char_traits<charT>,
    class Allocator = allocator<chatT> >
class basic_stringbuf : public basic_streambuf<charT, traits>
{
public:
// Les types :
    typedef charT                     char_type;
    typedef typename traits::int_type int_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;
    typedef traits                    traits_type;

// Les constructeurs / destructeurs :
    explicit basic_stringbuf(
        ios_base::openmode mode = ios_base::in | ios_base::out);
    explicit basic_stringbuf(
        const basic_string<charT,traits,Allocator> &str,
        ios_base::openmode mode = ios_base::in | ios_base::out);
    virtual ~basic_stringbuf();

// Les méthodes de gestion de la chaîne de caractères sous contrôle :
    basic_string<charT,traits,Allocator> str() const;
    void str(const basic_string<charT,traits,Allocator> &s);
};

Comme cette déclaration le montre, la classe basic_streambuf définit elle aussi un jeu de types permettant d'obtenir facilement les types de objets manipulés. De plus, elle définit également quelques méthodes complémentaires permettant d'effectuer les opérations spécifiques aux flux orientés chaîne de caractères. En particulier, les constructeurs permettent de fournir une chaîne de caractères à partir de laquelle le tampon sera initialisé. Cette chaîne de caractères est copiée lors de la construction du tampon, ce qui fait qu'elle peut être réutilisée ou modifiée après la création du tampon. Ces constructeurs prennent également en paramètre le mode de fonctionnement du tampon. Ce mode peut être la lecture (auquel cas le paramètre mode vaut ios_base::in), l'écriture (mode vaut alors ios_base::out) ou les deux (mode vaut alors la combinaison de ces deux constantes par un ou logique). Les constantes de mode d'ouverture sont définis dans la classe ios_base, que l'on décrira dans la Section 15.3.

Note : Vous remarquerez que, contrairement au constructeur de la classe basic_streambuf, les constructeurs de la classe basic_stringbuf sont déclarés dans la zone de déclaration publique, ce qui autorise la création de tampons de type basic_stringbuf. Le constructeur de la classe de base est appelé par ces constructeurs, qui ont le droit de le faire puisqu'il s'agit d'une méthode protected.

Il est également possible d'accéder aux données stockées dans le tampon à l'aide des accesseurs str. Le premier renvoie une basic_string contenant la chaîne du tampon en écriture si possible (c'est-à-dire si le tampon a été créé dans le mode écriture ou lecture / écriture), et la chaîne du tampon en lecture sinon. Le deuxième accesseur permet de définir les données du tampon a posteriori, une fois celui-ci créé.

Exemple 15-1. Lecture et écriture dans un tampon de chaîne de caractères

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main(void)
{
    // Construit une chaîne de caractère :
    string s("123456789");
    // Construit un tampon basé sur cette chaîne :
    stringbuf sb(s);
    // Lit quelques caractères unitairement :
    cout << (char) sb.sbumpc() << endl;
    cout << (char) sb.sbumpc() << endl;
    cout << (char) sb.sbumpc() << endl;
    // Replace le dernier caractère lu dans le tampon :
    sb.sungetc();
    // Lit trois caractères consécutivement :
    char tab[4];
    sb.sgetn(tab, 3);
    tab[3] = 0;
    cout << tab << endl;
    // Écrase le premier caractère de la chaîne :
    sb.sputc('a');
    // Récupère une copie de la chaîne utilisée par le tampon :
    cout << sb.str() << endl;
    return 0;
}

Note : La classe basic_stringbuf redéfinit bien entendu certaines des méthodes protégées de la classe basic_streambuf. Ces méthodes n'ont pas été présentées dans la déclaration ci-dessus parce qu'elles font partie de l'implémentation de la classe basic_stringbuf et leur description n'a que peu d'intérêt pour les utilisateurs.

15.2.3.2. La classe basic_filebuf

La classe basic_filebuf est la classe qui prend en charge les opérations d'entrée / sortie sur fichier dans la bibliothèque standard C++.

Pour la bibliothèque standard C++, un fichier est une séquence de caractères simples (donc de type char). Il est important de bien comprendre qu'il n'est pas possible, avec la classe basic_filebuf, de manipuler des fichiers contenant des données de type wchar_t. En effet, même dans le cas où les données enregistrées sont de type wchar_t, les fichiers contenant ces données sont enregistrés sous la forme de séquences de caractères dont l'unité de base reste le caractère simple. La manière de coder les caractères larges dans les fichiers n'est pas spécifiée et chaque implémentation est libre de faire ce qu'elle veut à ce niveau. Généralement, l'encodage utilisé est un encodage à taille variable, c'est à dire que chaque caractère large est représenté sous la forme d'un ou de plusieurs caractères simples, selon sa valeur et selon sa position dans le flux de données du fichier.

Cela signifie qu'il ne faut pas faire d'hypothèse sur la manière dont les instances de la classe template basic_filebuf enregistrent les données des fichiers pour des valeurs du paramètre template charT autres que le type char. En général, l'encodage utilisé ne concerne pas le programmeur, puisqu'il suffit d'enregistrer et de lire les fichiers avec les même types de classes basic_filebuf pour retrouver les données initiales. Toutefois, si les fichiers doivent être relus par des programmes écrits dans un autre langage ou compilés avec un autre compilateur, il peut être nécessaire de connaître l'encodage utilisé. Vous trouverez cette information dans la documentation de votre environnement de développement.

La classe basic_filebuf est déclarée comme suit dans l'en-tête fstream :

template <class charT,
    class traits = char_traits<charT> >
class basic_filebuf : public basic_streambuf<charT,traits>
{
public:
// Les types :
    typedef charT                     char_type;
    typedef typename traits::int_type int_type;
    typedef typename traits::pos_type pos_type;
    typedef typename traits::off_type off_type;
    typedef traits                    traits_type;

// Les constructeurs / destructeurs :
    basic_filebuf();
    virtual ~basic_filebuf();

// Les méthodes de gestion du fichier sous contrôle :
    basic_filebuf<charT,traits> *open(const char *s,
        ios_base::openmode mode);
    basic_filebuf<charT,traits> *close();
    bool is_open() const;
};

Comme vous pouvez le constater, la classe basic_filebuf est semblable à la classe basic_stringbuf. Outre les déclarations de types et celles du constructeur et du destructeur, elle définit trois méthodes permettant de réaliser les opérations spécifiques aux fichiers.

La méthode open permet, comme son nom l'indique, d'ouvrir un fichier. Cette méthode prend en paramètre le nom du fichier à ouvrir ainsi que le mode d'ouverture. Ce mode peut être une combinaison logique de plusieurs constantes définies dans la classe ios_base. Ces constantes sont décrites dans la Section 15.3. Les plus importantes sont in, qui permet d'ouvrir un fichier en lecture, out, qui permet de l'ouvrir en lecture, binary, qui permet de l'ouvrir en mode binaire, app, qui permet de l'ouvrir en mode ajout, et trunc, qui permet de le vider lorsqu'il est ouvert en écriture. La méthode open renvoie le pointeur this si le fichier a pu être ouvert ou le pointeur nul dans le cas contraire.

La classe basic_filebuf ne gère qu'une seule position pour la lecture et l'écriture dans les fichiers. Autrement dit, si un fichier est ouvert à la fois en lecture et en écriture, les pointeurs de lecture et d'écriture du tampon auront toujours la même valeur. L'écriture à une position provoquera donc non seulement la modification de la position courante en écriture, mais également celle de la position courante en lecture.

La méthode close est la méthode à utiliser pour fermer un fichier ouvert. Cette méthode ne peut fonctionner que si un fichier est effectivement ouvert dans ce tampon. Elle renvoie le pointeur this si le fichier courant a effectivement pu être fermé ou le pointeur nul en cas d'erreur.

Enfin, la méthode is_open permet de déterminer si un fichier est ouvert ou non dans ce tampon.

Exemple 15-2. Lecture et écriture dans un tampon de fichier

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main(void)
{
    // Ouvre un fichier texte et crée un tampon pour y accéder :
    filebuf fb;
    fb.open("test.txt", ios_base::in | ios_base::out | ios_base::trunc);
    // Teste si le fichier est ouvert :
    if (fb.is_open())
    {
        // Écrit deux lignes :
        string l1 = "Bonjour\n";
        string l2 = "tout le monde !\n";
        fb.sputn(l1.data(), l1.size());
        fb.sputn(l2.data(), l2.size());
        // Repositionne le pointeur de fichier au début.
        // Note : le déplacement se fait pour les deux
        // tampons parce qu'il n'y a qu'un pointeur
        // sur les données du fichier :
        fb.pubseekpos(0, ios_base::in | ios_base::out);
        // Lit les premières lettres du fichier :
        cout << (char) fb.sbumpc() << endl;
        cout << (char) fb.sbumpc() << endl;
        cout << (char) fb.sbumpc() << endl;
        // Ferme le fichier :
        fb.close();
    }
    return 0;
}