Cours de C/C++


précédentsommairesuivant

15. Les flux d'entrée / sortie

Nous avons vu dans la Section 8.12 un exemple d'application des classes de flux d'entrée / sortie de la bibliothèque pour les entrées / sorties standards des programmes. En réalité, ces classes de gestion des flux s'intègrent dans une hiérarchie complexe de classes permettant de manipuler les flux d'entrée / sortie et pas seulement pour les entrées / sorties standards.

En effet, afin de faciliter la manipulation des flux d'entrée / sortie, la bibliothèque standard C++ fournit tout un ensemble de classes template. Ces classes sont paramétrées par le type de base des caractères qu'elles manipulent. Bien entendu, les types de caractères les plus utilisés sont les type char et wchar_t, mais il est possible d'utiliser a priori n'importe quel autre type de donnée pour lequel une classe de traits char_traits est définie.

Ce chapitre a pour but de détailler cette hiérarchie de classes. Les principes de base et l'architecture générale des flux C++ seront donc abordés dans un premier temps, puis les classes de gestion des tampons seront traitées. Les classes génériques de gestion des flux d'entrée / sortie seront ensuite décrites, et ce sera enfin le tour des classes de gestion des flux orientés chaînes de caractères et des classes de gestion des flux orientés fichiers.

15.1. Notions de base et présentation générale

Les classes de la bibliothèque d'entrée / sortie de la bibliothèque standard se subdivisent en deux catégories distinctes.

La première catégorie regroupe les classes de gestion des tampons d'entrée / sortie. Ces classes sont au nombre de trois : la classe template basic_stringbuf, qui permet de réaliser des tampons pour les flux orientés chaînes de caractères, la classe template basic_filebuf, qui prend en charge les tampons pour les flux orientés fichiers, et leur classe de base commune, la classe template basic_streambuf. Le rôle de ces classes est principalement d'optimiser les entrées / sorties en intercalant des tampons d'entrée / sortie au sein même du programme. Ce sont principalement des classes utilitaires, qui sont utilisées en interne par les autres classes de la bibliothèque d'entrée / sortie.

La deuxième catégorie de classes est de loin la plus complexe, puisqu'il s'agit des classes de gestion des flux eux-mêmes. Toutes ces classes dérivent de la classe template basic_ios (elle-même dérivée de la classe de base ios_base, qui définit tous les types et les constantes utilisés par les classes de flux). La classe basic_ios fournit les fonctionnalités de base des classes de flux et, en particulier, elle gère le lien avec les tampons d'entrée / sortie utilisés par le flux. De cette classe de base dérivent des classes spécialisées respectivement pour les entrées ou pour les sorties. Ainsi, la classe template basic_istream prend en charge toutes les opérations des flux d'entrée et la classe basic_ostream toutes les opérations des flux de sortie. Enfin, la bibliothèque standard définit la classe template basic_iostream, qui regroupe toutes les fonctionnalités des classes basic_istream et basic_ostream et dont dérivent toutes les classes de gestion des flux mixtes.

Les classes basic_istream, basic_ostream et basic_iostream fournissent les fonctionnalités de base des flux d'entrée / sortie. Ce sont donc les classes utilisées pour implémenter les flux d'entrée / sortie standards du C++ cin, cout, cerr et clog, que l'on a brièvement présentés dans la Section 8.12. Cependant, ces classes ne prennent pas en charge toutes les spécificités des médias avec lesquels des flux plus complexes peuvent communiquer. Par conséquent, des classes dérivées, plus spécialisées, sont fournies par la bibliothèque standard. Ces classes prennent en charge les entrées / sorties sur fichier et les flux orientés chaînes de caractères.

La bibliothèque standard fournit donc deux jeux de classes spécialisées pour les entrées / sorties dans des fichiers et dans des chaînes de caractères. Pour chacune des classes de base basic_istream, basic_ostream et basic_iostream il existe deux classes dérivées, une pour les fichiers, et une pour les chaînes de caractères. Par exemple, les classes template basic_ifstream et basic_istringstream dérivent de la classe basic_istream et prennent en charge respectivement les flux d'entrée à partir de fichiers et les flux d'entrée à partir de chaînes de caractères. De même, la bibliothèque standard définit les classes template basic_ofstream et basic_ostringstream, dérivées de la classe basic_ostream, pour les flux de sortie dans des fichiers ou dans des chaînes de caractères, et les classes template basic_fstream et basic_stringstream, dérivées de la classe basic_iostream, pour les flux d'entrée / sortie sur les fichiers et les chaînes de caractères.

Note : Cette hiérarchie de classes est assez complexe et peut paraître étrange au niveau des classes des flux mixtes. En effet, la classe basic_fstream ne dérive pas des classes basic_ifstream et basic_ofstream mais de la classe basic_iostream, et c'est cette classe qui dérive des classes basic_istream et basic_ostream. De même, la classe basic_stringstream ne dérive pas des classes basic_istringstream et basic_ostringstream, mais de la classe basic_iostream.

Comme il l'a déjà été dit, toutes ces classes template peuvent être instanciées pour n'importe quel type de caractère, pourvu qu'une classe de traits char_traits soit définie. Cependant, en pratique, il n'est courant d'instancier ces classes que pour les types de caractères de base du langage, à savoir les types char et wchar_t.

Historiquement, les classes d'entrée / sortie des bibliothèques fournies avec la plupart des implémentations n'étaient pas template et ne permettaient de manipuler que des flux basés sur le type de caractère char. Les implémentations disposant de classes de flux d'entrée / sortie capables de manipuler les caractères de type wchar_t étaient donc relativement rares. À présent, toutes ces classes sont définies comme des instances des classes template citées ci-dessus. Par souci de compatibilité, la bibliothèque standard C++ définit tout un jeu de types pour ces instances qui permettent aux programmes utilisant les anciennes classes de fonctionner. Ces types sont déclarés de la manière suivante dans l'en-tête iosfwd (mais sont définis dans leurs en-têtes respectifs, que l'on décrira plus tard) :

 
Sélectionnez

// Types de base des tampons :
typedef basic_streambuf<char>    streambuf;
typedef basic_streambuf<wchar_t> wstreambuf;
typedef basic_stringbuf<char>    stringbuf;
typedef basic_stringbuf<wchar_t> wstringbuf;
typedef basic_filebuf<char>      filebuf;
typedef basic_filebuf<wchar_t>   wfilebuf;
 
// Types de base des flux d'entrée / sortie :
typedef basic_ios<char>    ios;
typedef basic_ios<wchar_t> wios;
 
// Types des flux d'entrée / sortie standards :
typedef basic_istream<char>     istream;
typedef basic_istream<wchar_t>  wistream;
typedef basic_ostream<char>     ostream;
typedef basic_ostream<wchar_t>  wostream;
typedef basic_iostream<char>    iostream;
typedef basic_iostream<wchar_t> wiostream;
 
// Types des flux orientés fichiers :
typedef basic_ifstream<char>    ifstream;
typedef basic_ifstream<wchar_t> wifstream;
typedef basic_ofstream<char>    ofstream;
typedef basic_ofstream<wchar_t> wofstream;
typedef basic_fstream<char>     fstream;
typedef basic_fstream<wchar_t>  wfstream;
 
// Types des flux orientés chaînes de caractères :
typedef basic_istringstream<char>    istringstream;
typedef basic_istringstream<wchar_t> wistringstream;
typedef basic_ostringstream<char>    ostringstream;
typedef basic_ostringstream<wchar_t> wostringstream;
typedef basic_stringstream<char>     stringstream;
typedef basic_stringstream<wchar_t>  wstringstream;
 

Les objets cin, cout, cerr et clog sont donc des instances des classes istream et ostream, qui sont associées aux flux d'entrée / sortie standards du programme. En fait, la bibliothèque standard définit également des versions capables de manipuler des flux basés sur le type wchar_t pour les programmes qui désirent travailler avec des caractères larges. Ces objets sont respectivement wcin (instance de wistream), wcout, wcerr et wclog (instances de wostream). Tous ces objets sont initialisés par la bibliothèque standard automatiquement lorsqu'ils sont utilisés pour la première fois, et sont donc toujours utilisables.

Note : En réalité, sur la plupart des systèmes, les flux d'entrée / sortie standards sont les premiers descripteurs de fichiers que le système attribue automatiquement aux programmes lorsqu'ils sont lancés. En toute logique, les objets cin, cout, cerr et clog devraient donc être des instances de classes de gestion de flux orientés fichiers. Cependant, ces fichiers ne sont pas nommés d'une part et, d'autre part, tous les systèmes ne gèrent pas les flux d'entrée / sortie standards de la même manière. Ces objets ne sont donc pas toujours des flux sur des fichiers et la bibliothèque standard C++ ne les définit par conséquent pas comme tels.

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 :

 
Sélectionnez

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éthode Description
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.
setbuf 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 :

 
Sélectionnez

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
Sélectionnez

#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 :

 
Sélectionnez

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
Sélectionnez

#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.
        // <b>Note :</b> 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;
}
 

15.3. Les classes de base des flux : ios_base et basic_ios

Les classes de gestion des flux constituent la deuxième hiérarchie de classes de la bibliothèque standard d'entrée / sortie. Bien que destinées à accéder à des médias variés, ces classes disposent d'une interface commune qui permet d'en simplifier l'utilisation. Cette interface est essentiellement définie par deux classes de bases fondamentales : la classe ios_base, qui définit toutes les fonctionnalités indépendantes du type de caractère utilisé par les flux, et la classe template basic_ios, qui regroupe l'essentiel des fonctionnalités des flux d'entrée / sortie.

15.3.1. La classe ios_base

La classe ios_base est une classe C++ classique dont toutes les classes template de gestion des flux d'entrée / sortie dérivent. Cette classe ne fournit, comme c'est le cas de la plupart des classes de base, qu'un nombre de fonctionnalités très réduit. En pratique, sa principale utilité est de définir plusieurs jeux de constantes qui sont utilisées par ses classes dérivées pour identifier les options des différents modes de fonctionnement disponibles. Ces constantes portent un nom standardisé mais leur type n'est pas précisé par la norme C++. Cependant, leur nature (entière, énumération, champ de bits) est imposée, et les implémentations doivent définir un typedef permettant de créer des variables du même type.

La classe de base ios_base est déclarée comme suit dans l'en-tête ios :

 
Sélectionnez

class ios_base
{
// Constructeur et destructeur :
protected:
    ios_base();
public:
    ~ios_base();
 
// Classe de base des exceptions des flux d'entrée / sortie :
    class failure;
 
// Classe d'initialisation des objets d'entrée / sortie standards :
    class Init;
 
// Constantes de définition des options de formatage :
    typedef T1 fmtflags;
    static const fmtflags boolalpha;
    static const fmtflags hex;
    static const fmtflags oct;
    static const fmtflags dec;
    static const fmtflags fixed;
    static const fmtflags scientific;
    static const fmtflags left;
    static const fmtflags right;
    static const fmtflags internal;
    static const fmtflags showbase;
    static const fmtflags showpoint;
    static const fmtflags showpos;
    static const fmtflags uppercase;
    static const fmtflags unitbuf;
    static const fmtflags skipws;
    static const fmtflags adjustfield;
    static const fmtflags basefield;
    static const fmtflags floatfield;
 
// Constantes des modes d'ouverture des flux et des fichiers :
    typedef T3 openmode;
    static const openmode in;
    static const openmode out;
    static const openmode binary;
    static const openmode trunc;
    static const openmode app;
    static const openmode ate;
 
// Constantes de définition des modes de positionnement :
    typedef T4 seekdir;
    static const seekdir beg;
    static const seekdir cur;
    static const seekdir end;
 
// Constantes d'état des flux d'entrée / sortie :
    typedef T2 iostate;
    static const iostate goodbit;
    static const iostate eofbit;
    static const iostate failbit;
    static const iostate badbit;
 
// Accesseurs sur les options de formatage :
    fmtflags flags() const;
    fmtflags flags(fmtflags fmtfl);
    fmtflags setf(fmtflags fmtfl);
    fmtflags setf(fmtflags fmtfl, fmtflags mask);
    void unsetf(fmtflags mask);
    streamsize precision() const;
    streamsize precision(streamsize prec);
    streamsize width() const;
    streamsize width(streamsize wide);
 
// Méthode de synchronisation :
    static bool sync_with_stdio(bool sync = true);
 
// Méthode d'enregistrement des callback pour les événements :
    enum event { erase_event, imbue_event, copyfmt_event };
    typedef void (*event_callback)(event, ios_base &, int index);
    void register_callback(event_call_back fn, int index);
 
// Méthode de gestion des données privées :
    static int xalloc();
    long &iword(int index);
    void* &pword(int index);
 
// Méthodes de gestion des locales :
    locale imbue(const locale &loc);
    locale getloc() const;
};
 

Comme vous pouvez le constater, le constructeur de la classe ios_base est déclaré en zone protégée. Il n'est donc pas possible d'instancier un objet de cette classe, ce qui est normal puisqu'elle n'est destinée qu'à être la classe de base de classes plus spécialisées.

Le premier jeu de constantes défini par la classe ios_base contient toutes les valeurs de type fmtflags, qui permettent de spécifier les différentes options à utiliser pour le formatage des données écrites dans les flux. Ce type doit obligatoirement être un champ de bits. Les constantes quant à elles permettent de définir la base de numérotation utilisée, si celle-ci doit être indiquée avec chaque nombre ou non, ainsi que les différentes options de formatage à utiliser. La signification précise de chacune de ces constantes est donnée dans le tableau suivant :

Tableau 15-1. Options de formatage des flux

Constante Signification
boolalpha Permet de réaliser les entrées / sorties des booléens sous forme textuelle et non sous forme numérique. Ainsi, les valeurs true et false ne sont pas écrites ou lues sous la forme de 0 ou de 1, mais sous la forme fixée par la classe de localisation utilisée par le flux. Par défaut, les booléens sont représentés par les chaînes de caractères « true » et « false » lorsque ce flag est actif. Cependant, il est possible de modifier ces chaînes de caractères en définissant une locale spécifique. Les notions de locales seront décrites dans le Chapitre 16.
hex Permet de réaliser les entrées / sorties des entiers en base hexadécimale.
oct Permet de réaliser les entrées / sorties des entiers en base octale.
dec Permet de réaliser les entrées / sorties des entiers en décimal.
fixed Active la représentation en virgule fixe des nombres à virgule flottante.
scientific Active la représentation en virgule flottante des nombres à virgule flottante.
left Utilise l'alignement à gauche pour les données écrites sur les flux de sortie. Dans le cas où la largeur des champs est fixée, des caractères de remplissage sont ajoutés à la droite de ces données pour atteindre cette largeur.
right Utilise l'alignement à droite pour les données écrites sur les flux de sortie. Dans le cas où la largeur des champs est fixée, des caractères de remplissage sont ajoutés à la gauche de ces données pour atteindre cette largeur.
internal Effectue un remplissage avec les caractères de remplissage à une position fixe déterminée par la locale en cours d'utilisation si la largeur des données est inférieure à la largeur des champs à utiliser. Si la position de remplissage n'est pas spécifiée par la locale pour l'opération en cours, le comportement adopté est l'alignement à droite.
showbase Précise la base utilisée pour le formatage des nombres entiers.
showpoint Écrit systématiquement le séparateur de la virgule dans le formatage des nombres à virgule flottante, que la partie fractionnaire de ces nombres soit nulle ou non. Le caractère utilisé pour représenter ce séparateur est défini dans la locale utilisée par le flux d'entrée / sortie. La notion de locale sera vue dans le Chapitre 16. Par défaut, le caractère utilisé est le point décimal ('.').
showpos Utilise systématiquement le signe des nombres écrits sur le flux de sortie, qu'ils soient positifs ou négatifs. Le formatage du signe des nombre se fait selon les critères définis par la locale active pour ce flux. Par défaut, les nombres négatifs sont préfixés du symbole '-' et les nombres positifs du symbole '+' si cette option est active. Dans le cas contraire, le signe des nombres positifs n'est pas écrit.
uppercase Permet d'écrire en majuscule certains caractères, comme le 'e' de l'exposant des nombres à virgule flottante par exemple, ou les chiffres hexadécimaux A à F.
unitbuf Permet d'effectuer automatiquement une opération de synchronisation du cache utilisé par le flux de sortie après chaque écriture.
skipws Permet d'ignorer les blancs précédant les données à lire dans les opérations d'entrée pour lesquelles de tels blancs sont significatifs.

La classe ios_base définit également les constantes adjustfield, basefield et floatfield, qui sont en réalité des combinaisons des autres constantes. Ainsi, la constante adjustfield représente l'ensemble des options d'alignement (à savoir left, right et internal), la constante basefield représente les options de spécification de base pour les sorties numériques (c'est-à-dire les options hex, oct et dec), et la constante floatfield les options définissant les types de formatage des nombres à virgules (scientific et fixed).

Le deuxième jeu de constantes permet de caractériser les modes d'ouverture des flux et des fichiers. Le type de ces constantes est le type openmode. Il s'agit également d'un champ de bits, ce qui permet de réaliser des combinaisons entre leurs valeurs pour cumuler différents modes d'ouverture lors de l'utilisation des fichiers. Les constantes définies par la classe ios_base sont décrites dans le tableau ci-dessous :

Tableau 15-2. Modes d'ouverture des fichiers

Constante Signification
in Permet d'ouvrir le flux en écriture.
out Permet d'ouvrir le flux en lecture.
binary Permet d'ouvrir le flux en mode binaire, pour les systèmes qui font une distinction entre les fichiers textes et les fichiers binaires. Ce flag n'est pas nécessaire pour les systèmes d'exploitation conformes à la norme POSIX. Cependant, il est préférable de l'utiliser lors de l'ouverture de fichiers binaires si l'on veut que le programme soit portable sur les autres systèmes d'exploitation.
trunc Permet de vider automatiquement le fichier lorsqu'une ouverture en écriture est demandée.
app Permet d'ouvrir le fichier en mode ajout lorsqu'une ouverture en écriture est demandée. Dans ce mode, le pointeur de fichier est systématiquement positionné en fin de fichier avant chaque écriture. Ainsi, les écritures se font les unes à la suite des autres, toujours à la fin du fichier, et quelles que soient les opérations qui peuvent avoir lieu sur le fichier entre-temps.
ate Permet d'ouvrir le fichier en écriture et de positionner le pointeur de fichier à la fin de celui-ci. Notez que ce mode de fonctionnement se distingue du mode app par le fait que si un repositionnement a lieu entre deux écritures la deuxième écriture ne se fera pas forcément à la fin du fichier.

Le troisième jeu de constantes définit les diverses directions qu'il est possible d'utiliser lors d'un repositionnement d'un pointeur de fichier. Le type de ces constantes, à savoir le type seekdir, est une énumération dont les valeurs sont décrites dans le tableau ci-dessous :

Tableau 15-3. Directions de déplacement dans un fichier

Constante Signification
beg Le déplacement de fait par rapport au début du fichier. Le décalage spécifié dans les fonctions de repositionnement doit être positif ou nul, la valeur 0 correspondant au début du fichier.
cur Le déplacement se fait relativement à la position courante. Le décalage spécifié dans les fonctions de repositionnement peut donc être négatif, positif ou nul (auquel cas aucun déplacement n'est effectué).
end Le déplacement se fait relativement à la fin du fichier. Le décalage fourni dans les fonctions de repositionnement doit être positif ou nul, la valeur 0 correspondant à la fin de fichier.

Enfin, les constantes de type iostate permettent de décrire les différents états dans lequel un flux d'entrée / sortie peut se trouver. Il s'agit, encore une fois, d'un champ de bits, et plusieurs combinaisons sont possibles.

Tableau 15-4. États des flux d'entrée / sortie

Constante Signinfication
goodbit Cette constante correspond à l'état normal du flux, lorsqu'il ne s'est produit aucune erreur.
eofbit Ce bit est positionné dans la variable d'état du flux lorsque la fin du flux a été atteinte, soit parce qu'il n'y a plus de données à lire, soit parce qu'on ne peut plus en écrire.
failbit Ce bit est positionné dans la variable d'état du flux lorsqu'une erreur logique s'est produite lors d'une opération de lecture ou d'écriture. Ceci peut avoir lieu lorsque les données écrites ou lues sont incorrectes.
badbit Ce bit est positionné lorsqu'une erreur fatale s'est produite. Ce genre de situation peut se produire lorsqu'une erreur a eu lieu au niveau matériel (secteur défectueux d'un disque dur ou coupure réseau par exemple).

Les différentes variables d'état des flux d'entrée / sortie peuvent être manipulées à l'aide de ces constantes et des accesseurs de la classe ios_base. Les méthodes les plus importantes sont sans doute celles qui permettent de modifier les options de formatage pour le flux d'entrée / sortie. La méthode flags permet de récupérer la valeur de la variable d'état contenant les options de formatage du flux. Cette méthode dispose également d'une surcharge qui permet de spécifier une nouvelle valeur pour cette variable d'état, et qui retourne la valeur précédente. Il est aussi possible de fixer et de désactiver les options de formatage indépendamment les unes des autres à l'aide des méthodes setf et unsetf. La méthode setf prend en paramètre les nouvelles options qui doivent être ajoutées au jeu d'options déjà actives, avec, éventuellement, un masque permettant de réinitialiser certaines autres options. On emploiera généralement un masque lorsque l'on voudra fixer un paramètre parmi plusieurs paramètres mutuellement exclusifs, comme la base de numérotation utilisée par exemple. La méthode unsetf prend quant à elle le masque des options qui doivent être supprimées du jeu d'options utilisé par le flux en paramètre.

Outre les méthodes de gestion des options de formatage, la classe ios_base définit deux surcharges pour chacune des méthodes precision et width. Ces méthodes permettent respectivement de lire et de fixer la précision avec laquelle les nombres à virgule doivent être écrits et la largeur minimale des conversions des nombres lors des écritures.

La plupart des options que l'on peut fixer sont permanentes, c'est-à-dire qu'elles restent actives jusqu'à ce qu'on spécifie de nouvelles options. Cependant, ce n'est pas le cas du paramètre de largeur que l'on renseigne grâce à la méthode width. En effet, chaque opération d'écriture réinitialise ce paramètre à la valeur 0. Il faut donc spécifier la largeur minimale pour chaque donnée écrite sur le flux.

Exemple 15-3. Modification des options de formatage des flux
Sélectionnez

#include <iostream>
using namespace std;
 
// Affiche un booléen, un nombre entier et un nombre à virgule :
void print(bool b, int i, float f)
{
    cout << b << " " << i << " " << f << endl;
}
 
int main(void)
{
    // Affiche avec les options par défaut :
    print(true, 35, 3105367.9751447);
    // Passe en hexadécimal :
    cout.unsetf(ios_base::dec);
    cout.setf(ios_base::hex);
    print(true, 35, 3105367.9751447);
    // Affiche la base des nombres et
    // affiche les booléens textuellement :
    cout.setf(ios_base::boolalpha);
    cout.setf(ios_base::showbase);
    print(true, 35, 3105367.9751447);
    // Affiche un flottant en notation à virgule fixe
    // avec une largeur minimale de 16 caractères :
    cout << "***";
    cout.width(16);
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << 315367.9751447;
    cout << "***" << endl;
    // Recommence en fixant la précision
    // à 3 chiffres et la largeur à 10 caractères :
    cout << "***";
    cout.precision(3);
    cout.width(10);
    cout << 315367.9751447;
    cout << "***" << endl;
    return 0;
}
 

Note : On prendra bien garde au fait que la largeur des champs dans lesquels les données sont écrites est une largeur minimale, pas une largeur maximale. En particulier, cela signigie que les écritures ne sont pas tronquées si elles sont plus grande que cette largeur. On devra donc faire extrêmement attention à ne pas provoquer de débordements lors des écritures.

On n'oubliera pas de s'assurer de la cohérence des paramètres du flux lorsqu'on modifie la valeur d'une option. Par exemple, dans l'exemple précédent, il faut désactiver l'emploi de la numérotation décimale lorsque l'on demande à utiliser la base hexadécimale. Cette opération a été faite explicitement ici pour bien montrer son importance, mais elle aurait également pu être réalisée par l'emploi d'un masque avec la constante ios_base::basefield. L'exemple précédent montre comment utiliser un masque avec l'appel à setf pour fixer la représentation des nombres à virgule.

La classe ios_base fournit également un certain nombre de services généraux au programmeur et à ses classes dérivées. La méthode sync_with_stdio permet de déterminer, pour un flux d'entrée / sortie standard, s'il est synchronisé avec le flux sous-jacent ou si des données se trouvent encore dans son tampon. Lorsqu'elle est appelée avec le paramètre false dès le début du programme, elle permet de décorréler le fonctionnement du flux C++ et du flux standard sous-jacent. Pour tous les autres appels, cette méthode ignore le paramètre qui lui est fourni. La méthode register_callback permet d'enregistrer une fonction de rappel qui sera appelée par la classe ios_base lorsque des événements susceptibles de modifier notablement le comportement du flux se produisent. Ces fonctions de rappel peuvent recevoir une valeur entière en paramètre qui peut être utilisée pour référencer des données privées contenant des paramètres plus complexes. Les méthodes xalloc, iword et pword sont fournies afin de permettre de stocker ces données privées et de les retrouver facilement à l'aide d'un indice, qui peut être la valeur passée en paramètre à la fonction de rappel. Ces méthodes permettent de récupérer des références sur des valeurs de type long et sur des pointeurs de type void. Enfin, la classe ios_base fournit une classe de base pour les exceptions que les classes de flux pourront utiliser afin de signaler une erreur et une classe permettant d'initialiser les objets cin, cout, cerr, clog et leurs semblables pour les caractères larges. Toutes ces fonctionnalités ne sont généralement pas d'une très grande utilité pour les programmeurs et sont en réalité fournie pour faciliter l'implémentation des classes de flux de la bibliothèque standard.

Enfin, la classe ios_base fournit les méthodes getloc et imbue qui permettent respectivement de récupérer la locale utilisée par le flux et d'en fixer une autre. Cette locale est utilisée par le flux pour déterminer la manière de représenter et de lire les nombres et pour effectuer les entrées / sorties formatées en fonction des paramètres de langue et des conventions locales du pays où le programme est exécuté. Les notions de locale et de paramètres internationaux seront décrits en détail dans le Chapitre 16.

15.3.2. La classe basic_ios

La classe template basic_ios fournit toutes les fonctionnalités communes à toutes les classes de flux de la bibliothèque d'entrée / sortie. Cette classe dérive de la classe ios_base et apporte tous les mécanismes de gestion des tampons pour les classes de flux. La classe basic_ios est déclarée comme suit dans l'en-tête ios :

 
Sélectionnez

template <class charT,
    class traits = char_traits<charT> >
class basic_ios : public ios_base
{
// Constructeur et destructeur :
protected:
    basic_ios();
    void init(basic_streambuf<charT,traits> *flux);
 
public:
// Types de données :
    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;
 
// Constructeur publique, destructeur et opération de copie :
    explicit basic_ios(basic_streambuf<charT,traits> *flux);
    virtual ~basic_ios();
    basic_ios &copyfmt(const basic_ios &);
 
// Méthodes de gestion des tampons :
    basic_streambuf<charT,traits> *rdbuf() const;
    basic_streambuf<charT,traits> *rdbuf(
        basic_streambuf<charT,traits> *tampon);
 
// Méthodes de gestion des exceptions :
    iostate exceptions() const;
    void exceptions(iostate except);
 
// Accesseurs :
    operator void*() const
    bool operator!() const
    iostate rdstate() const;
    void clear(iostate statut = goodbit);
    void setstate(iostate statut);
    bool good() const;
    bool eof()  const;
    bool fail() const;
    bool bad()  const;
    char_type fill() const;
    char_type fill(char_type c);
    basic_ostream<charT,traits> *tie() const;
    basic_ostream<charT,traits> *tie(
        basic_ostream<charT,traits> *flux);
 
// Méthodes de gestion des locales :
    locale imbue(const locale &loc);
    char   narrow(char_type c, char defaut) const;
    char_type widen(char c) const;
};
 

Le constructeur de base ainsi que la méthode init, destinée à associer un tampon à un flux après sa construction, sont déclarés en zone protégée. Ainsi, il n'est pas possible d'instancier et d'initialiser manuellement un flux avec un tampon. Cependant, la classe basic_ios fournit un constructeur en zone publique qui permet de créer un nouveau flux et de l'initialiser à la volée. Ce constructeur prend en paramètre l'adresse d'un tampon qui sera associé au flux après la construction. Remarquez que, bien qu'il soit ainsi parfaitement possible d'instancier un objet de type l'une des instances de la classe template basic_ios, cela n'a pas grand intérêt. En effet, cette classe ne fournit pas assez de fonctionnalités pour réaliser des entrées / sorties facilement sur le flux. La classe basic_ios est donc réellement destinée à être utilisée en tant que classe de base pour des classes plus spécialisées dans les opérations d'entrée / sortie.

Une fois initialisés, les flux sont associés aux tampons grâce auxquels ils accèdent aux médias physiques pour leurs opérations d'entrée / sortie. Cependant, cette association n'est pas figée et il est possible de changer de tampon a posteriori. Les manipulations de tampon sont effectuées avec les deux surcharges de la méthode rdbuf. La première permet de récupérer l'adresse de l'objet tampon courant et la deuxième d'en spécifier un nouveau. Cette dernière méthode renvoie l'adresse du tampon précédent. Bien entendu, le fait de changer le tampon d'un flux provoque sa réinitialisation.

Les flux de la bibliothèque standard peuvent signaler les cas d'erreurs aux fonctions qui les utilisent de différentes manières. La première est simplement de renvoyer un code d'erreur (false ou le caractère de fin de fichier), et la deuxième est de lancer une exception dérivée de la classe d'exception failure (définie dans la classe de base ios_base). Ce comportement est paramétrable en fonction des types d'erreurs qui peuvent se produire. Par défaut, les classes de flux n'utilisent pas les exceptions, quelles que soient les erreurs rencontrées. Toutefois, il est possible d'activer le mécanisme des exceptions individuellement pour chaque type d'erreur possible. La classe basic_ios gère pour cela un masque d'exceptions qui peut être récupéré et modifié à l'aide de deux méthodes surchargées. Ces méthodes sont les méthodes exceptions. La première version renvoie le masque courant et la deuxième permet de fixer un nouveau masque. Les masques d'exceptions sont constitués de combinaisons logiques des bits d'état des flux définis dans la classe ios_base (à savoir goodbit, eofbit, failbit et badbit). Le fait de changer le masque d'exceptions réinitialise l'état du flux.

La classe basic_ios fournit également tout un ensemble d'accesseurs grâce auxquels il est possible de récupérer l'état courant du flux. Ces accesseurs sont principalement destinés à faciliter la manipulation du flux et à simplifier les différentes expressions dans lesquelles il est utilisé. Par exemple, l'opérateur de transtypage vers le type pointeur sur void permet de tester la validité du flux comme s'il s'agissait d'un pointeur. Cet opérateur retourne en effet une valeur non nulle si le flux est utilisable (c'est-à-dire si la méthode fail renvoie false. De même, l'opérateur de négation operator! renvoie la même valeur que la méthode fail.

Comme vous l'aurez sans doute compris, la méthode fail indique si le flux (et donc le tampon contrôlé par ce flux) est dans un état correct. En pratique, cette méthode renvoie true dès que l'un des bits ios_base::failbit ou ios_base::badbit est positionné dans la variable d'état du flux. Vous pourrez faire la distinction entre ces deux bits grâce à la méthode bad, qui elle ne renvoie true que si le bit ios_base::badbit est positionné. Les autres méthodes de lecture de l'état du flux portent des noms explicites et leur signification ne doit pas poser de problème. On prendra toutefois garde à bien distinguer la méthode clear, qui permet de réinitialiser l'état du flux avec le masque de bits passé en paramètre, de la méthode setstate, qui permet de positionner un bit complémentaire. Ces deux méthodes sont susceptibles de lancer des exceptions si le nouvel état du flux le requiert et si son masque d'exceptions l'exige.

Le dernier accesseur utile pour le programmeur est l'accesseur fill. Cet accesseur permet de lire la valeur du caractère de remplissage utilisé lorsque la largeur des champs est supérieure à la largeur des données qui doivent être écrites sur le flux de sortie. Par défaut, ce caractère est le caractère d'espacement.

Note : Les deux surcharges de la méthode tie permettent de stocker dans le flux un pointeur sur un flux de sortie standard avec lequel les opérations d'entrée / sortie doivent être synchronisées. Ces méthodes sont utilisées en interne par les méthodes d'entrée / sortie des classes dérivées de la classe basic_ios et ne sont pas réellement utiles pour les programmeurs. En général donc, seule les classes de la bibliothèque standard les appelleront.

Enfin, la classe basic_ios prend également en compte la locale du flux dans tous ses traitements. Elle redéfinit donc la méthode imbue afin de pouvoir détecter les changement de locale que l'utilisateur peut faire. Bien entendu, la méthode getloc est héritée de la classe de base ios_base et permet toujours de récupérer la locale courante. De plus, la classe basic_ios définit deux méthodes permettant de réaliser les conversions entre le type de caractère char et le type de caractère fourni en paramètre template. La méthode widen permet, comme son nom l'indique, de convertir un caractère de type char en un caractère du type template du flux. Inversement, la méthode narrow permet de convertir un caractère du type de caractère du flux en un caractère de type char. Cette méthode prend en paramètre le caractère à convertir et la valeur par défaut que doit prendre le résultat en cas d'échec de la conversion.

15.4. Les flux d'entrée / sortie

La plupart des fonctionnalités des flux d'entrée / sortie sont implémentées au niveau des classes template basic_ostream et basic_istream. Ces classes dérivent toutes deux directement de la classe basic_ios, dont elles héritent de toutes les fonctionnalités de gestion des tampons et de gestion d'état.

Les classes basic_ostream et basic_istream seront sans doute les classes de flux que vous utiliserez le plus souvent, car c'est à leur niveau que sont définies toutes les fonctionnalités de lecture et d'écriture sur les flux, aussi bien pour les données formatées telles que les entiers, les flottants ou les chaînes de caractères, que pour les écritures de données brutes. De plus, les flux d'entrée / sortie standards cin, cout, cerr et clog sont tous des instances de ces classes.

La bibliothèque standard définit également une classe capable de réaliser à la fois les opérations de lecture et d'écriture sur les flux : la classe basic_iostream. En fait, cette classe dérive simplement des deux classes basic_istream et basic_ostream, et regroupe donc toutes les fonctionnalités de ces deux classes.

15.4.1. La classe de base basic_ostream

La classe basic_ostream fournit toutes les fonctions permettant d'effectuer des écritures sur un flux de sortie, que ces écritures soient formatées ou non. Elle est déclarée comme suit dans l'en-tête ostream :

 
Sélectionnez

template <class charT,
    class traits = char_traits<charT> >
class basic_ostream : virtual public basic_ios<charT, traits>
{
public:
// Les types de données :
    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;
 
// Le constructeur et le destructeur :
    explicit basic_ostream(basic_streambuf<char_type, traits> *tampon);
    virtual ~basic_ostream();
 
// Les opérations d'écritures formatées :
    basic_ostream<charT, traits> &operator<<(bool);
    basic_ostream<charT, traits> &operator<<(short);
    basic_ostream<charT, traits> &operator<<(unsigned short);
    basic_ostream<charT, traits> &operator<<(int);
    basic_ostream<charT, traits> &operator<<(unsigned int);
    basic_ostream<charT, traits> &operator<<(long);
    basic_ostream<charT, traits> &operator<<(unsigned long);
    basic_ostream<charT, traits> &operator<<(float);
    basic_ostream<charT, traits> &operator<<(double);
    basic_ostream<charT, traits> &operator<<(long double);
    basic_ostream<charT, traits> &operator<<(void *);
    basic_ostream<charT, traits> &operator<<
          (basic_streambuf<char_type, traits> *tampon);
 
// Classe de gestion des exceptions pour les opérateurs d'écritures formatées :
    class sentry
    {
    public:
        explicit sentry(basic_ostream<charT, traits> &);
        ~sentry();
        operator bool();
    };
 
// Les opérations d'écritures non formatées :
    basic_ostream<charT, traits> &put(char_type);
    basic_ostream<charT, traits> &write(const char_type *p, streamsize taille);
 
// Les opérations de gestion du tampon :
    basic_ostream<charT, traits> &flush();
    pos_type tellp();
    basic_ostream<charT, traits> &seekp(pos_type);
    basic_ostream<charT, traits> &seekp(off_type, ios_base::seekdir);
 
// Les opérations de gestion des manipulateurs :
    basic_ostream<charT, traits> &operator<<
          (basic_ostream<charT, traits> & (*pf)(
	      basic_ostream<charT, traits> &));
    basic_ostream<charT, traits> &operator<<
          (basic_ios<charT, traits> & (*pf)(basic_ios<charT, traits> &));
    basic_ostream<charT, traits> &operator<<
          (ios_base & (*pf)(ios_base &));
};
 

Comme vous pouvez le constater, le constructeur de cette classe prend en paramètre un pointeur sur l'objet tampon dans lequel les écritures devront être réalisées. Vous pouvez donc construire un flux de sortie à partir de n'importe quel tampon, simplement en fournissant ce tampon en paramètre au constructeur. Cependant, il ne faut pas procéder ainsi en général, mais utiliser plutôt les classes dérivées de la classe basic_ostream et spécialisées dans les écritures sur fichiers et dans des chaînes de caractères.

Les écritures formatées sont réalisées par l'intermédiaire de différentes surcharges de l'opérateur d'insertion operator<<, dont il existe une version pour chaque type de donnée de base du langage. Ainsi, l'écriture d'une valeur dans le flux de sortie se fait extrêmement simplement :

 
Sélectionnez

// Écriture d'une chaîne de caractères sur le flux de sortie standard :
cout << "Voici la valeur d'un entier :\n";
 
// Écriture d'un entier sur le flux de sortie standard :
cout << 45;
 

Vous constaterez que, grâce aux mécanismes de surcharge, ces écritures se font exactement de la même manière pour tous les types de données. On ne peut faire plus simple...

Note : Les opérations de formatage prennent en compte toutes les options de formatage qui sont stockées dans la classe de base ios_base. En général, les opérations d'écriture ne modifient pas ces options. Toutefois, la largeur minimale des champs dans lesquels les résultats sont formatés est systématiquement réinitialisée à 0 après chaque écriture. Il est donc nécessaire, lorsque l'on réalise plusieurs écritures formatées dans un flux de sortie, de spécifier pour chaque valeur sa largeur minimale si elle ne doit pas être égale à 0. Autrement dit, il faut redire la largeur de chaque champ, même s'ils utilisent tous la même largeur minimale.

Les opérations de formatage utilisent également les conventions locales du pays où le programme est exécuté, conventions qui sont définies dans la locale incluse dans le flux de sortie. Les notions de locale seront détaillées dans le Chapitre 16.

Bien entendu, il est possible de définir de nouvelles surcharges de l'opérateur d'insertion pour les types définis par l'utilisateur, ce qui permet d'étendre à l'infini les possibilités de cette classe. Ces surcharges devront obligatoirement être définies à l'extérieur de la classe template basic_ostream et, si l'on veut les écrire de manière générique, elles devront également être des fonctions template paramétrées par le type de caractère du flux sur lequel elles travaillent.

Vous noterez la présence d'une classe sentry dans la classe basic_ostream. Cette classe est une classe utilitaire permettant de réaliser les initialisations qui doivent précéder toutes les opérations d'écriture au sein des surcharges de l'opérateur d'insertion. Entre autres opérations, le constructeur de cette classe peut synchroniser le flux de sortie standard encapsulé dans le flux grâce à la méthode tie de la classe de base basic_ios, et prendre toutes les mesures devant précéder les écritures sur le flux. Vous devrez donc toujours utiliser un objet local de ce type lorsque vous écrirez une surcharge de l'opérateur operator<< pour vos propres types. Les écritures ne devront être réalisées que si l'initialisation a réussi, ce qui peut être vérifié simplement en comparant l'objet local de type sentry avec la valeur true.

Exemple 15-4. Définition d'un nouvel opérateur d'insertion pour un flux de sortie
Sélectionnez

#include <iostream>
#include <string>
 
using namespace std;
 
// Définition d'un type de donnée privé :
struct Personne
{
    string Nom;
    string Prenom;
    int Age;       // En centimètres.
    int Taille;
};
 
// Définition de l'opérateur d'écriture pour ce type :
template <class charT, class Traits>
basic_ostream<charT, Traits> &operator<<(
    basic_ostream<charT, Traits> &flux,
    const Personne &p)
{
    // Inialisation du flux de sortie :
    typename basic_ostream<charT, Traits>::sentry init(flux);
    if (init)
    {
        // Écriture des données :
        int Metres = p.Taille / 100;
        int Reste = p.Taille % 100;
        flux << p.Prenom << " " << p.Nom <<
            " mesure " << Metres <<
            "m" << Reste << " (" <<
            p.Age << " an";
        if (p.Age > 1) flux << "s";
        flux << ")";
    }
    return flux;
}
 
int main(void)
{
    // Construit une nouvelle personne :
    Personne p;
    p.Nom = "Dupont";
    p.Prenom = "Jean";
    p.Age = 28;
    p.Taille = 185;
    // Affiche les caractéristiques de cette personne :
    cout << p << endl;
    return 0;
}
 

Note : L'utilisation de l'objet local de type sentry comme un booléen est autorisée parce que la classe sentry définit un opérateur de transtypage vers le type bool.

Le constructeur de la classe sentry est susceptible de lancer des exceptions, selon la configuration du masque d'exceptions du flux de sortie avec lequel on l'initialise.

Les écritures de données brutes ne disposent bien entendu pas de surcharges pour chaque type de donnée, puisqu'il s'agit dans ce cas d'écrire les données directement sur le flux de sortie, sans les formater sous forme textuelle. Ces écritures sont donc réalisées par l'intermédiaire de méthodes dédiées qui effectuent soit l'écriture d'un caractère unique, soit l'écriture d'un tableau de caractères complet. Pour écrire un unique caractère sur le flux de sortie, vous pouvez utiliser la méthode put. Pour l'écriture d'un bloc de données en revanche, il faut utiliser la méthode write, qui prend en paramètre un pointeur sur la zone de données à écrire et la taille de cette zone.

Exemple 15-5. Écriture de données brutes sur un flux de sortie
Sélectionnez

#include <iostream>
using namespace std;
 
// Définition de quelques codes de couleurs
// pour les terminaux ANSI :
const char Rouge[] = {033, '[', '3', '1', 'm'};
const char Vert[] = {033, '[', '3', '2', 'm'};
const char Jaune[] = {033, '[', '3', '3', 'm'};
const char Reset[] = {033, '[', 'm', 017};
 
int main(void)
{
    // Écriture d'un message coloré :
    cout.write(Rouge, sizeof(Rouge));
    cout << "Bonjour ";
    cout.write(Vert, sizeof(Vert));
    cout << "tout ";
    cout.write(Jaune, sizeof(Jaune));
    cout << "le monde !" << endl;
    cout.write(Reset, sizeof(Reset));
    return 0;
}
 

Bien entendu, la classe basic_ostream fournit les méthodes nécessaires à la gestion du tampon sous-jacent. La méthode flush permet de synchroniser le tampon utilisé par le flux (en appelant la méthode pubsync de ce dernier). La méthode tellp permet de lire la position courante dans le flux de sortie, et les deux surcharges de la méthode seekp permettent de modifier cette position soit de manière absolue, soit de manière relative à la position courante. Nous verrons un exemple d'utilisation de ces méthodes dans la description des classes de flux pour les fichiers.

La classe basic_ostream définit également des surcharges de l'opérateur d'insertion capables de prendre en paramètre des pointeurs de fonctions. Ces méthodes ne constituent pas des opérations d'écriture à proprement parler, mais permettent de réaliser des opérations sur les flux de sortie plus facilement à l'aide de fonctions capables de les manipuler. En raison de cette propriété, ces fonctions sont couramment appelées des manipulateurs. En réalité, ces manipulateurs ne sont rien d'autre que des fonctions prenant un flux en paramètre et réalisant des opérations sur ce flux. Les opérateurs operator<< prenant en paramètre ces manipulateurs les exécutent sur l'objet courant *this. Ainsi, il est possible d'appliquer ces manipulateurs à un flux simplement en réalisant une écriture du manipulateur sur ce flux, exactement comme pour les écritures normales.

La bibliothèque standard définit tout un jeu de manipulateurs extrêmement utiles pour définir les options de formatage et pour effectuer des opérations de base sur les flux. Grâce à ces manipulateurs, il n'est plus nécessaire d'utiliser la méthode setf de la classe ios_base par exemple. Par exemple, le symbole endl utilisé pour effectuer un retour à la ligne dans les opérations d'écriture sur les flux de sortie n'est rien d'autre qu'un manipulateur, dont la déclaration est la suivante :

 
Sélectionnez

template <class charT, class Traits>
basic_ostream<charT, Traits> &endl(
    basic_ostream<charT, Traits> &flux);
 

et dont le rôle est simplement d'écrire le caractère de retour à la ligne et d'appeler la méthode flush du flux de sortie.

Il existe des manipulateurs permettant de travailler sur la classe de base ios_base ou sur ses classes dérivées comme la classe basic_ostream par exemple, d'où la présence de plusieurs surcharges de l'opérateur d'insertion pour ces différents manipulateurs. Il existe également des manipulateurs prenant des paramètres et renvoyant un type de données spécial pour lequel un opérateur d'écriture a été défini, et qui permettent de réaliser des opérations plus complexes nécessitant des paramètres complémentaires. Les manipulateurs sont définis, selon leur nature, soit dans l'en-tête de déclaration du flux, soit dans l'en-tête ios, soit dans l'en-tête iomanip.

Le tableau suivant présente les manipulateurs les plus simples qui ne prennent pas de paramètre :

Tableau 15-5. Manipulateurs des flux de sortie

Manipulateur Fonction
endl Envoie un caractère de retour à la ligne sur le flux et synchronise le tampon par un appel à la méthode flush.
ends Envoie un caractère nul terminal de fin de ligne sur le flux.
flush Synchronise le tampon utilisé par le flux par un appelle à la méthode flush.
boolalpha Active le formatage des booléens sous forme textuelle.
noboolalpha Désactive le formatage textuel des booléens.
hex Formate les nombres en base 16.
oct Formate les nombres en base 8.
dec Formate les nombres en base 10.
fixed Utilise la notation en virgule fixe pour les nombres à virgule.
scientific Utilise la notation en virgule flottante pour les nombres à virgule.
left Aligne les résultats à gauche.
right Aligne les résultats à droite.
internal Utilise le remplissage des champs avec des espaces complémentaires à une position fixe déterminée par la locale courante. Équivalent à right si la locale ne spécifie aucune position de remplissage particulière.
showbase Indique la base de numérotation utilisée
noshowbase N'indique pas la base de numérotation utilisée.
showpoint Utilise le séparateur de virgule dans les nombres à virgule, même si la partie fractionnaire est nulle.
noshowpoint N'utilise le séparateur de virgule que si la partie fractionnaire des nombres à virgule flottante est significative.
showpos Écrit systématiquement le signe des nombres, même s'ils sont positifs.
noshowpos N'écrit le signe des nombres que s'ils sont négatifs.
uppercase Écrit les exposants et les chiffres hexadécimaux en majuscule.
nouppercase Écrit les exposants et les chiffres hexadécimaux en minuscule.
unitbuf Effectue une opération de synchronisation du cache géré par le tampon du flux après chaque écriture.
nounitbuf N'effectue les opérations de synchronisation du cache géré par le tampon du flux que lorsque cela est explicitement demandé.

Les paramètres suivants sont un peu plus complexes, puisqu'ils prennent des paramètres complémentaires. Ils renvoient un type de donnée spécifique à chaque implémentation de la bibliothèque standard et qui n'est destiné qu'à être inséré dans un flux de sortie à l'aide de l'opérateur d'insertion :

Tableau 15-6. Manipulateurs utilisant des paramètres

Manipulateur Fonction
resetiosflags(ios_base::fmtflags) Permet d'effacer certains bits des options du flux. Ces bits sont spécifiés par une combinaison logique de constantes de type ios_base::fmtflags.
setiosflags(ios_base::fmtflags) Permet de positionner certains bits des options du flux. Ces bits sont spécifiés par une combinaison logique de constantes de type ios_base::fmtflags.
setbase(int base) Permet de sélectionner la base de numérotation utilisée. Les valeurs admissibles sont 8, 10 et 16 respectivement pour la base octale, la base décimale et la base hexadécimale.
setprecision(int) Permet de spécifier la précision (nombre de caractères significatifs) des nombres formatés.
setw(int) Permet de spécifier la largeur minimale du champ dans lequel la donnée suivante sera écrite à la prochaine opération d'écriture sur le flux.
setfill(char_type) Permet de spécifier le caractère de remplissage à utiliser lorsque la largeur des champs est inférieure à la largeur minimale spécifiée dans les options de formatage.
Exemple 15-6. Utilisation des manipulateurs sur un flux de sortie
Sélectionnez

#include <iostream>
#include <iomanip>
 
using namespace std;
 
int main(void)
{
    // Affiche les booléens sous forme textuelle :
    cout << boolalpha << true << endl;
    // Écrit les nombres en hexadécimal :
    cout << hex << 57 << endl;
    // Repasse en base 10 :
    cout << dec << 57 << endl;
    // Affiche un flottant avec une largeur
    // minimale de 15 caractères :
    cout << setfill('*') << setw(15) << 3.151592 << endl;
    // Recommence mais avec un alignement à gauche :
    cout << left << setw(15) << 3.151592 << endl;
}
 

15.4.2. La classe de base basic_istream

La deuxième classe la plus utilisée de la bibliothèque d'entrée / sortie est sans doute la classe template basic_istream. À l'instar de la classe ostream, cette classe fournit toutes les fonctionnalités de lecture de données formatées ou non à partir d'un tampon. Ce sont donc certainement les méthodes cette classe que vous utiliserez le plus souvent lorsque vous désirerez lire les données d'un flux. La classe basic_istream est déclarée comme suit dans l'en-tête istream :

 
Sélectionnez

template <class charT,
    class traits = char_traits<charT> >
class basic_istream : virtual public basic_ios<charT, traits>
{
public:
// Les types de données :
    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;
 
// Le constructeur et destructeur :
    explicit basic_istream(basic_streambuf<charT, traits> *sb);
    virtual ~basic_istream();
 
// Les opération de gestion des entrées formatées :
    basic_istream<charT, traits> &operator>>(bool &n);
    basic_istream<charT, traits> &operator>>(short &n);
    basic_istream<charT, traits> &operator>>(unsigned short &n);
    basic_istream<charT, traits> &operator>>(int &n);
    basic_istream<charT, traits> &operator>>(unsigned int &n);
    basic_istream<charT, traits> &operator>>(long &n);
    basic_istream<charT, traits> &operator>>(unsigned long &n);
    basic_istream<charT, traits> &operator>>(float &f);
    basic_istream<charT, traits> &operator>>(double &f);
    basic_istream<charT, traits> &operator>>(long double &f);
    basic_istream<charT, traits> &operator>>(void * &p);
    basic_istream<charT, traits> &operator>>
        (basic_streambuf<char_type, traits> *sb);
 
// Classe de gestion des exceptions pour les opérateurs d'écritures formatées :
    class sentry
    {
    public:
        explicit sentry(basic_istream<charT, traits> &flux,
            bool conserve = false);
        ~sentry();
        operator bool();
    };
 
// Les opérations de lecture des données brutes :
    int_type get();
    basic_istream<charT, traits> &get(char_type &c);
    int_type peek();
    basic_istream<charT, traits> &putback(char_type c);
    basic_istream<charT, traits> &unget();
    basic_istream<charT, traits> &read(char_type *s, streamsize n);
    streamsize                   readsome(char_type *s, streamsize n);
 
    basic_istream<charT, traits> &get(char_type *s, streamsize n);
    basic_istream<charT, traits> &get(char_type *s, streamsize n,
        char_type delim);
    basic_istream<charT, traits> &get(
        basic_streambuf<char_type, traits> &sb);
    basic_istream<charT, traits> &get(
        basic_streambuf<char_type, traits> &sb, char_type delim);
    basic_istream<charT, traits> &getline(char_type *s, streamsize n);
    basic_istream<charT, traits> &getline(char_type *s, streamsize n,
        char_type delim);
    basic_istream<charT, traits> &ignore
        (streamsize n = 1, int_type delim = traits::eof());
 
    streamsize gcount() const;
 
// Les opérations de gestion du tampon :
    int sync();
    pos_type tellg();
    basic_istream<charT, traits> &seekg(pos_type);
    basic_istream<charT, traits> &seekg(off_type, ios_base::seekdir);
 
// Les opérations de gestion des manipulateurs :
    basic_istream<charT, traits> &operator>>
        (basic_istream<charT, traits> & (*pf)(
	    basic_istream<charT, traits> &));
    basic_istream<charT, traits> &operator>>
        (basic_ios<charT, traits> & (*pf)(basic_ios<charT, traits> &));
    basic_istream<charT, traits> &operator>>
        (ios_base & (*pf)(ios_base &));
};
 

Tout comme la classe basic_ostream, le constructeur de la classe basic_istream prend en paramètre un pointeur sur l'objet gérant le tampon dans lequel les écritures devront être effectuées. Cependant, même s'il est possible de créer une instance de flux d'entrée simplement à l'aide de ce constructeur, cela n'est pas recommandé puisque la bibliothèque standard fournit des classes spécialisées permettant de créer des flux de sortie orientés fichiers ou chaînes de caractères.

L'utilisation des différentes surcharges de l'opérateur d'extraction des données formatées operator>> ne devrait pas poser de problème. Le compilateur détermine la surcharge à utiliser en fonction du type des données à lire, déterminé par la référence de variable fournie en paramètre. Cette surcharge récupère alors les informations dans le tampon associé au flux, les interprète et écrit la nouvelle valeur dans la variable.

Bien entendu, tout comme pour la classe basic_ostream, il est possible d'écrire de nouvelles surcharges de l'opérateur d'extraction afin de prendre en charge de nouveaux types de données. Idéalement, ces surcharges devront être également des fonctions template paramétrées par le type de caractère du flux sur lequel elles travaillent, et elles devront également utiliser une classe d'initialisation sentry. Cette classe a principalement pour but d'initialiser le flux d'entrée, éventuellement en le synchronisant avec un flux de sortie standard dont la classe basic_ostream peut être stockée dans le flux d'entrée à l'aide de la méthode tie de la classe de base basic_ios, et en supprimant les éventuels caractères blancs avant la lecture des données.

Notez que, contrairement à la classe sentry des flux de sortie, le constructeur de la classe sentry des flux d'entrée prend un deuxième paramètre. Ce paramètre est un booléen qui indique si les caractères blancs présents dans le flux de données doivent être éliminés avant l'opération de lecture ou non. En général, pour les opérations de lecture formatées, ce sont des caractères non significatifs et il faut effecivement supprimer ces caractères, aussi la valeur à spécifier pour ce second paramètre est-elle false. Comme c'est aussi la valeur par défaut, la manière d'utiliser de la classe sentry dans les opérateurs d'extraction est strictement identique à celle de la classe sentry des opérateurs d'insertion de la classe basic_ostream.

Exemple 15-7. Écriture d'un nouvel opérateur d'extraction pour un flux d'entrée
Sélectionnez

#include <iostream>
#include <string>
 
using namespace std;
 
// Définition d'un type de donnée privé :
struct Personne
{
    string Nom;
    string Prenom;
    int Age;       // En centimètres.
    int Taille;
};
 
// Définition de l'opérateur de lecture pour ce type :
template <class charT, class Traits>
basic_istream<charT, Traits> &operator>>(
    basic_istream<charT, Traits> &flux, Personne &p)
{
    // Inialisation du flux de sortie :
    typename basic_istream<charT, Traits>::sentry init(flux);
    if (init)
    {
        // Lecture du prénom et du nom :
        flux >> p.Prenom;
        flux >> p.Nom;
        // Lecture de l'âge :
        flux >> p.Age;
        // Lecture de la taille en mètres :
        double Taille;
        flux >> Taille;
        // Conversion en centimètres ;
        p.Taille = (int) (Taille * 100 + 0.5);
    }
    return flux;
}
 
int main(void)
{
    // Construit une nouvelle personne :
    Personne p;
    // Demande la saisie d'une personne :
    cout << "Prénom Nom Âge(ans) Taille(m) : ";
    cin >> p;
    // Affiche les valeurs lues :
    cout << endl;
    cout << "Valeurs saisies :" << endl;
    cout << p.Prenom << " " << p.Nom << " a " <<
        p.Age << " ans et mesure " <<
        p.Taille << " cm." << endl;
    return 0;
}
 

Note : La classe sentry est également utilisée par les méthodes de lecture de données non formatées. Pour ces méthodes, les caractères blancs sont importants et dans ce cas le second paramètre fourni au constructeur de la classe sentry est true.

Comme pour la classe sentry de la classe basic_ostream, l'utilisation de l'objet d'initialisation dans les tests est rendue possible par la présence de l'opérateur de transtypage vers le type bool. La valeur retournée est true si l'initialisation s'est bien faite et false dans le cas contraire. Remarquez également que le constructeur de la classe sentry est susceptible de lancer des exceptions selon la configuration du masque d'exceptions dans la classe de flux.

Les opérations de lecture de données non formatées sont un peu plus nombreuses pour les flux d'entrée que les opérations d'écriture non formatées pour les flux de sortie. En effet, la classe basic_istream donne non seulement la possibilité de lire un caractère simple ou une série de caractères, mais aussi de lire les données provenant du tampon de lecture et de les interpréter en tant que « lignes ». Une ligne est en réalité une série de caractères terminée par un caractère spécial que l'on nomme le marqueur de fin de ligne. En général, ce marqueur est le caractère '\n', mais il est possible de spécifier un autre caractère.

La lecture d'un caractère unique dans le flux d'entrée se fait à l'aide de la méthode get. Il existe deux surcharges de cette méthode, la première ne prenant aucun paramètre et renvoyant le caractère lu, et la deuxième prenant en paramètre une référence sur la variable devant recevoir le caractère lu et ne renvoyant rien. Ces deux méthodes extraient les caractères qu'elles lisent du tampon d'entrée que le flux utilise. Si l'on veut simplement lire la valeur du caractère suivant sans l'en extraire, il faut appeler la méthode peek. De plus, tout caractère extrait peut être réinséré dans le flux d'entrée (pourvu que le tampon sous-jacent accepte cette opération) à l'aide de l'une des deux méthodes unget ou putback. Cette dernière méthode prend en paramètre le caractère qui doit être réinséré dans le flux d'entrée. Notez que la réinsertion ne peut être réalisée que si le caractère fourni en paramètre est précisément le dernier caractère extrait.

Si l'on désire réaliser la lecture d'une série de caractères au lieu de les extraire un à un, il faut utiliser la méthode read. Cette méthode est la méthode de base pour les lectures non formatées puisqu'elle lit les données brutes de fonderie, sans les interpréter. Elle prend en paramètre un pointeur sur un tableau de caractères dans lequel les données seront écrites et le nombre de caractères à lire. Cette méthode ne vérifie pas la taille du tableau spécifié, aussi celui-ci doit-il être capable d'accueillir le nombre de caractères demandé. Il existe une variante de la méthode read, la méthode readsome, qui permet de lire les données présentes dans le tampon géré par le flux sans accéder au média que ce dernier prend en charge. Cette méthode prend également en paramètre un pointeur sur la zone mémoire devant recevoir les données et le nombre de caractères désiré, mais, contrairement à la méthode read, elle peut ne pas lire exactement ce nombre. En effet, la méthode readsome s'arrête dès que le tampon utilisé par le flux est vide, ce qui permet d'éviter les accès sur le périphérique auquel ce tampon donne accès. La méthode readsome renvoie le nombre de caractères effectivement lus.

Les méthodes de lecture des lignes sont à diviser en deux catégories. La première catégorie, constituée de plusieurs surcharges de la méthode get, permet d'effectuer une lecture des données du tampon jusqu'à ce que le tableau fourni en paramètre soit rempli ou qu'une fin de ligne soit atteinte. La deuxième catégorie de méthodes est constituée des surcharges de la méthode getline. Ces méthodes se distinguent des méthodes get par le fait qu'elles n'échouent pas lorsque la ligne lue (délimiteur de ligne compris) remplit complètement le tableau fourni en paramètre d'une part, et par le fait que le délimiteur de ligne est extrait du tampon d'entrée utilisé par le flux d'autre part. Autrement dit, si une ligne complète (c'est-à-dire avec son délimiteur) a une taille exactement égale à la taille du tableau fourni en paramètre, les méthodes get échoueront alors que les méthodes getline réussiront, car elles ne considèrent pas le délimiteur comme une information importante. Ceci revient à dire que les méthodes getline interprètent complètement le caractère délimiteur, alors que les méthodes get le traitent simplement comme le caractère auquel la lecture doit s'arrêter.

Dans tous les cas, un caractère nul terminal est inséré en lieu et place du délimiteur dans le tableau fourni en paramètre et devant recevoir les données. Comme le deuxième paramètre de ces méthodes indique la dimension de ce tableau, le nombre de caractères lu est au plus cette dimension moins un. Le nombre de caractères extraits du tampon d'entrée est quant à lui récupérable grâce à la méthode gcount. Remarquez que le caractère de fin de ligne est compté dans le nombre de caractères extraits pour les méthodes getline, alors qu'il ne l'est pas pour les méthodes get puisque ces dernières ne l'extraient pas du tampon.

Enfin, il est possible de demander la lecture d'un certain nombre de caractères et de les passer sans en récupérer la valeur. Cette opération est réalisable à l'aide de la méthode ignore, qui ne prend donc pas de pointeurs sur la zone mémoire où les caractères lus doivent être stockés puisqu'ils sont ignorés. Cette méthode lit autant de caractères que spécifié, sauf si le caractère délimiteur indiqué en deuxième paramètre est rencontré. Dans ce cas, ce caractère est extrait du tampon d'entrée, ce qui fait que la méthode ignore se comporte exactement comme les méthodes getline.

Exemple 15-8. Lectures de lignes sur le flux d'entrée standard
Sélectionnez

#include <iostream>
#include <sstream>
 
using namespace std;
 
int main(void)
{
    // Tableau devant recevoir une ligne :
    char petit_tableau[10];
    // Lit une ligne de 9 caractères :
    cout << "Saisissez une ligne :" << endl;
    cin.getline(petit_tableau, 10);
    if (cin.fail())
        cout << "Ligne trop longue !" << endl;
    cout << "Lu : ***" << petit_tableau << "***" << endl;
    // Lit une ligne de taille arbitraire via un tampon :
    cout << "Saisissez une autre ligne :" << endl;
    stringbuf s;
    cin.get(s);
    // Affiche la ligne lue :
    cout << "Lu : ***" << s.str() << "***";
    // Extrait le caractère de saut de ligne
    // et ajoute-le au flux de sortie standard :
    cout << (char) cin.get();
    return 0;
}
 

Note : Remarquez que le caractère de saut de ligne étant lu, il est nécessaire de saisir deux retours de chariot successifs pour que la méthode getline renvoie son résultat. Comme pour toutes les méthodes de lectures formatées, ce caractère interrompt la lecture dans le flux d'entrée standard du programme et se trouve donc encore dans le tampon d'entrée lors de la lecture suivante. Cela explique que dans le cas de lectures successives, il faut extraire ce caractère du flux d'entrée manuellement, par exemple à l'aide de la méthode get. C'est ce que cet exemple réalise sur sa dernière ligne pour l'envoyer sur le flux de sortie standard.

De plus, on ne peut pas prévoir, a priori, quelle sera la taille des lignes saisies par l'utilisateur. On ne procédera donc pas comme indiqué dans cet exemple pour effectuer la lecture de lignes en pratique. Il est en effet plus facile d'utiliser la fonction getline, que l'on a décrit dans la Section 14.1.8 dans le cadre du type basic_string. En effet, cette fonction permet de lire une ligne complète sans avoir à se soucier de sa longueur maximale et de stocker le résultat dans une basic_string.

La classe basic_istream dispose également de méthodes permettant de manipuler le tampon qu'elle utilise pour lire de nouvelles données. La méthode sync permet de synchroniser le tampon d'entrée avec le média auquel il donne accès, puisqu'elle appelle la méthode pubsync de ce tampon. Pour les flux d'entrée, cela n'a pas réellement d'importance parce que l'on ne peut pas écrire dedans. La méthode tellg permet de déterminer la position du pointeur de lecture courant, et les deux surcharges de la méthode seekg permettent de repositionner ce pointeur. Nous verrons un exemple d'utilisation de ces méthodes dans la description des classes de flux pour les fichiers.

Enfin, les flux d'entrée disposent également de quelques manipulateurs permettant de les configurer simplement à l'aide de l'opérateur operator>>. Ces manipulateurs sont présentés dans le tableau ci-dessous :

Tableau 15-7. Manipulateurs des flux d'entrée

Manipulateur Fonction
boolalpha Active l'interprétation des booléens sous forme de textuelle.
noboolalpha Désactive l'interprétation des booléens sous forme textuelle.
hex Utilise la base 16 pour l'interprétation des nombres entiers.
oct Utilise la base 8 pour l'interprétation des nombres entiers.
dec Utilise la base 10 pour l'interprétation des nombres entiers.
skipws Ignore les espaces lors des entrées formatées.
noskipws Conserve les espaces lors des entrées formatées.
ws Supprime tous les espaces présents dans le flux d'entrée jusqu'au premier caractère non blanc.

Ces manipulateurs s'utilisent directement à l'aide de l'opérateur operator>>, exactement comme les manipulateurs de la classe basic_ostream s'utilisent avec l'opérateur d'insertion normal.

15.4.3. La classe basic_iostream

La bibliothèque standard définit dans l'en-tête iostream la classe template basic_iostream afin de permettre à la fois les opérations d'écriture et les opérations de lecture sur les flux. En fait, cette classe n'est rien d'autre qu'une classe dérivée des deux classes basic_ostream et basic_istream qui fournissent respectivement, comme on l'a vu, toutes les fonctionnalités de lecture et d'écriture sur un tampon.

La classe basic_iostream ne comporte pas d'autres méthodes qu'un constructeur et un destructeur, qui servent uniquement à initialiser et à détruire les classes de base basic_ostream et basic_istream. L'utilisation de cette classe ne doit donc pas poser de problème particulier et je vous invite à vous référer aux descriptions des classes de base si besoin est.

Note : Tout comme ses classes de base, la classe basic_iostream sera rarement utilisée directement. En effet, elle dispose de classes dérivées spécialisées dans les opérations d'écriture et de lecture sur fichiers ou dans des chaînes de caractères, classes que l'on présentera dans les sections suivantes. Ce sont ces classes que l'on utilisera en pratique lorsque l'on désirera créer un nouveau flux pour lire et écrire dans un fichier ou dans une basic_string.

Vous aurez peut-être remarqué que les classes basic_ostream et basic_istream utilisent un héritage virtuel pour récupérer les fonctionnalités de la classe de base basic_ios. La raison en est que la classe basic_iostream réalise un héritage multiple sur ses deux classes de base et que les données de la classe basic_ios ne doivent être présente qu'en un seul exemplaire dans les flux d'entrée / sortie. Cela implique que les constructeurs des classes dérivées de la classe basic_iostream prenant des paramètres doivent appeler explicitement les constructeurs de toutes leur classes de base. Voyez la Section 8.6 pour plus de détails sur les notions d'héritage multiple et de classes virtuelles.

15.5. Les flux d'entrée / sortie sur chaînes de caractères

Afin de donner la possibilité aux programmeurs d'effectuer les opérations de formatage des données en mémoire aussi simplement qu'avec les classes de gestion des flux d'entrée / sortie standards, la bibliothèque d'entrée / sortie définit trois classes de flux capables de travailler dans des chaînes de caractères de type basic_string. Ces classes sont les classes basic_ostringstream, pour les écritures dans les chaînes de caractères, basic_istringstream, pour les lectures de données stockées dans les chaînes de caractères, et basic_stringstream, pour les opérations à la fois d'écriture et de lecture.

Ces classes dérivent respectivement des classes de flux basic_istream, basic_ostream et basic_iostream et reprennent donc à leur compte toutes les fonctions de formatage et d'écriture de ces classes. Les écritures et les lectures de données en mémoire se font donc, grâce à ces classes, aussi facilement qu'avec les flux d'entrée / sortie standards, et ce de manière complètement transparente.

En fait, les classes de flux orientées chaînes de caractères fonctionnent exactement comme leurs classes de base, car toutes les fonctionnalités de gestion des chaînes de caractères sont encapsulées au niveau des classes de gestion des tampons qui ont été présentées au début de ce chapitre. Cependant, elles disposent de méthodes spécifiques qui permettent de manipuler les chaînes de caractères sur lesquelles elles travaillent. Par exemple, la classe basic_ostringstream est déclarée comme suit dans l'en-tête sstream :

 
Sélectionnez

template <class charT,
    class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
class basic_ostringstream : public basic_ostream<charT, traits>
{
public:
// Les types de données :
    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;
 
// Les constructeurs et destructeurs :
    explicit basic_ostringstream(ios_base::openmode mode = ios_base::out);
    explicit basic_ostringstream(
        const basic_string<charT, traits, Allocator> &chaine,
        ios_base::openmode mode = ios_base::out);
    virtual ~basic_ostringstream();
 
// Les méthodes de gestion de la chaîne de caractères :
    basic_stringbuf<charT, traits, Allocator> *rdbuf() const;
    basic_string<charT, traits, Allocator> str() const;
    void    str(const basic_string<charT, traits, Allocator> &chaine);
};
 

Les classes basic_istringstream et basic_stringstream sont déclarées de manière identique, à ceci près que les classes de base et les valeurs par défaut pour les modes d'ouverture du tampon sur la chaîne de caractères ne sont pas les mêmes.

Comme vous pouvez le constater, il est possible de construire un flux d'entrée / sortie sur une chaîne de caractères de différentes manières. La première méthode est de construire un objet flux, puis de préciser, pour les flux d'entrée, la chaîne de caractères dans laquelle les données à lire se trouvent à l'aide de la méthode str. La deuxième méthode est tout simplement de fournir tous les paramètres en une seule fois au constructeur. En général, les valeurs par défaut spécifiées dans les constructeurs correspondent à ce que l'on veut faire avec les flux, ce qui fait que la construction de ces flux est extrêmement simple.

Une fois construit, il est possible de réaliser toutes les opérations que l'on désire sur le flux. Dans le cas des flux d'entrée, il est nécessaire que le flux ait été initialisé avec une chaîne de caractères contenant les données à lire, et l'on ne cherche généralement pas à récupérer cette chaîne après usage du flux. Pour les flux de sortie, cette initialisation est inutile. En revanche, le résultat des opérations de formatage sera généralement récupéré à l'aide de la méthode str une fois celles-ci réalisées.

Exemple 15-9. Utilisation de flux d'entrée / sortie sur chaînes de caractères
Sélectionnez

#include <iostream>
#include <sstream>
#include <string>
 
using namespace std;
 
int main(void)
{
    // Lit une ligne en entrée :
    cout << "Entier Réel Chaîne : ";
    string input;
    getline(cin, input);
    // Interprète la ligne lue :
    istringstream is(input);
    int i;
    double d;
    string s;
    is >> i >> d;
    is >> ws;
    getline(is, s);
    // Formate la réponse :
    ostringstream os;
    os << "La réponse est : " << endl;
    os << s << " " << 2*i << " " << 2*d << endl;
    // Affiche la chaîne de la réponse :
    cout << os.str();
    return 0;
}
 

Comme l'exemple précédent vous le montre, l'utilisation des flux d'entrée / sortie de la bibliothèque standard sur les chaînes de caractères est réellement aisée. Comme ces opérations ne peuvent être réalisées qu'en mémoire, c'est à dire en dehors du contexte du système d'exploitation utilisé, les classes de flux de la bibliothèque restent utiles même si les opérations d'entrée / sortie du système se font de telle manière que les objets cin et cout ne sont pratiquement pas utilisables.

15.6. Les flux d'entrée / sortie sur fichiers

Les classes d'entrée / sortie sur les fichiers sont implémentées de manière similaire aux classes d'entrée / sortie sur les chaînes de caractères, à ceci près que leurs méthodes spécifiques permettent de manipuler un fichier au lieu d'une chaîne de caractères. Ainsi, la classe basic_ofstream dérive de basic_ostream, la classe basic_ifstream de la classe basic_istream, et la classe basic_fstream de la classe basic_iostream. Toutes ces classes sont déclarées dans l'en-tête fstream. Vous trouverez à titre d'exemple la déclaration de la classe basic_ofstream tel qu'il apparaît dans cet en-tête :

 
Sélectionnez

template <class charT,
    class traits = char_traits<charT> >
class basic_ofstream : public basic_ostream<charT, traits>
{
public:
// Les types de données :
    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;
 
// Les constructeurs et destructeurs :
    basic_ofstream();
    explicit basic_ofstream(const char *nom,
        ios_base::openmode mode = ios_base::out | ios_base::trunc);
 
// Les méthodes de gestion du fichier :
    basic_filebuf<charT, traits> *rdbuf() const;
    bool is_open();
    void open(const char *nom, ios_base::openmode mode = out | trunc);
    void close();
};
 

Comme pour les flux d'entrée / sortie sur les chaînes de caractères, il est possible d'initialiser le flux dès sa construction ou a posteriori. Les méthodes importantes sont bien entendu la méthode open, qui permet d'ouvrir un fichier, la méthode is_open, qui permet de savoir si le flux contient déjà un fichier ouvert ou non, et la méthode close, qui permet de fermer le fichier ouvert.

Exemple 15-10. Utilisation de flux d'entrée / sortie sur un fichier
Sélectionnez

#include <iostream>
#include <fstream>
#include <string>
 
using namespace std;
 
int main(void)
{
    // Lit les données :
    int i;
    double d, e;
    cout << "Entier Réel Réel : ";
    cin >> i >> d >> e;
    // Enregistre ces données dans un fichier :
    ofstream f("fichier.txt");
    if (f.is_open())
    {
        f << "Les données lues sont : " <<
            i << " " << d << " " << e << endl;
        f.close();
    }
    return 0;
}
 

Note : Il est important de bien noter que le destructeur des flux ne ferme pas les fichiers. En effet, ce sont les tampons utilisés de manière sous-jacente par les flux qui réalisent les opérations sur les fichiers. Il est même tout à fait possible d'accéder à un même fichier avec plusieurs classes de flux, bien que cela ne soit pas franchement recommandé. Vous devrez donc prendre en charge vous-même les opérations d'ouverture et de fermeture des fichiers.

Bien entendu, les classes de flux permettant d'accéder à des fichiers héritent des méthodes de positionnement de leurs classes de base. Ainsi, les classes de lecture dans un fichier disposent des méthodes tellg et seekg, et les classes d'écriture disposent des méthodes tellp et seekp. Ces opérations permettent respectivement de lire et de fixer une nouvelle valeur du pointeur de position du fichier courant.

Exemple 15-11. Repositionnement du pointeur de fichier dans un flux d'entrée / sortie
Sélectionnez

#include <iostream>
#include <fstream>
#include <string>
 
using namespace std;
 
int main(void)
{
    // Ouvre le fichier de données :
    fstream f("fichier.txt",
        ios_base::in | ios_base::out | ios_base::trunc);
    if (f.is_open())
    {
        // Écrit les données :
        f << 2 << " " <<  45.32 << " " << 6.37 << endl;
        // Replace le pointeur de fichier au début :
        f.seekg(0);
        // Lit les données :
        int i;
        double d, e;
        f >> i >> d >> e;
        cout << "Les données lues sont : " <<
            i << " " << d << " " << e << endl;
        // Ferme le fichier :
        f.close();
    }
    return 0;
}
 

Note : Les classes d'entrée / sortie sur fichier n'utilisent qu'un seul tampon pour accéder aux fichiers. Par conséquent, il n'existe qu'une seule position dans le fichier, qui sert à la fois à la lecture et à l'écriture.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 developpez.com Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.