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 :

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

ConstanteSignification
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

ConstanteSignification
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

ConstanteSignification
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

ConstanteSignification
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

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

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.