• Les facettes standarts

    16.2. Les facettes standards

    Cette section présente l'ensemble des facettes standards définies par la bibliothèque standard. La première partie décrit l'architecture générale à laquelle les facettes standards se conforment, et les parties suivantes donnent une description des principales fonctionnalités fournies par chacune des catégories de facettes.

    16.2.1. Généralités

    Les facettes fournies par la bibliothèque standard sont des classes template paramétrées par le type des caractères sur lesquels elles travaillent. Pour quelques-unes de ces facettes, la bibliothèque standard définit une spécialisation pour les types char ou wchar_t, spécialisation dont le but est d'optimiser les traitements des facettes pour les flux d'entrée / sortie standards sur ces types.

    Certaines de ces facettes ne sont utilisées que pour fournir des informations aux autres parties de la bibliothèque standard C++. D'autres, en revanche, permettent de réaliser les opérations de formatage et d'analyse syntaxique sur lesquelles les flux d'entrée / sortie s'appuient pour implémenter les opérateurs operator<< et operator>>. Ces facettes disposent alors de méthodes put et get qui permettent d'effectuer ces deux types d'opération.

    Les traitements effectués par les facettes doivent prendre en compte les paramètres de leur locale ainsi que les options de formatage stockées dans les flux sur lesquelles les entrées / sorties doivent être effectuées. Pour cela, les facettes doivent disposer d'un moyen de récupérer la locale dont elles font partie ou dont elles doivent utiliser les paramètres. Généralement, les facettes qui réalisent les opérations d'entrée / sortie pour le compte des flux utilisent la méthode getloc de ces derniers pour obtenir la locale dont elles doivent utiliser les paramètres. Les autres facettes ne peuvent pas procéder de la même manière, car elles ne disposent pas forcément d'un objet flux pour déterminer la locale courante. Afin de résoudre ce problème, la bibliothèque standard définit des classes de facettes dérivées et dont le constructeur prend en paramètre le nom de la locale à laquelle ces facettes appartiennent. Ces classes sont donc initialisées, dès leur construction, avec le nom de la locale dans laquelle elles se trouvent, ce qui leur permet éventuellement d'effectuer des traitements dépendants de cette locale. Les noms de ces classes de facettes dérivées sont les mêmes que ceux de leurs classes de base, à ceci près qu'ils sont suffixés par la chaîne « _byname ». Par exemple, la facette ctype, qui, comme on le verra plus loin, permet de classer les caractères selon leur nature, dispose d'une classe dérivée ctype_byname dont le constructeur prend en paramètre le nom de la locale dont la facette fait partie.

    Note : Les implémentations de la bibliothèque standard fournies avec les environnements de développement C++ ne sont pas tenues de fournir ces facettes pour chaque locale existante dans le monde. En réalité, quasiment aucun environnement ne le fait à l'heure actuelle. En revanche, toutes les facettes standards doivent au moins être fournies et fonctionner correctement avec les locales "C" et "".

    Les facettes sont écrites de telle manière qu'elles peuvent facilement être remplacées par des facettes plus spécifiques. Ainsi, leurs méthodes publiques appellent toutes des méthodes virtuelles, qui peuvent parfaitement être redéfinies par des classes dérivées désirant remplacer l'un des traitements effectués par la bibliothèque standard.

    Généralement, les noms des méthodes virtuelles sont les mêmes que ceux des méthodes publiques qui les utilisent, précédés du préfixe « do_ ». Par exemple, si une facette fournit une méthode publique nommée put, la méthode virtuelle appelée par celle-ci se nommera do_put. La manière de redéfinir les méthodes d'une facette existante et de remplacer cette facette par une de ses classes dérivées dans une locale sera décrite en détail dans la section 16.3.2.

    16.2.2. Les facettes de manipulation des caractères

    La bibliothèque standard définit deux facettes permettant de manipuler les caractères. La première facette, la facette ctype, fournit les fonctions permettant de classer les caractères en différentes catégories. Ces catégories comprennent les lettres, les chiffres, les caractères imprimables, les caractères graphiques, etc. La deuxième facette permet quant à elle d'effectuer les conversions entre les différents types existants d'encodage de caractères. Il s'agit de la facette code_cvt.

    16.2.2.1. La facette ctype

    La facette ctype dérive d'une classe de base dans laquelle sont définies les différentes catégories de caractères. Cette classe est déclarée comme suit dans l'en-tête locale :

    class ctype_base
    {
    public:
    enum mask
    {
    space = SPACE_VALUE, print = PRINT_VALUE,
    cntrl = CNTRL_VALUE, alpha = ALPHA_VALUE,
    digit = DIGIT_VALUE, xdigit = XDIGIT_VALUE,
    upper = UPPER_VALUE, lower = LOWER_VALUE,
    punct = PUNCT_VALUE,
    alnum = alpha | digit, graph = alnum | punct
    };
    };
    Les valeurs numériques utilisées par cette énumération sont définies de telle manière que les constantes de type mask constituent un champ de bits. Ainsi, il est possible de définir des combinaisons entre ces valeurs, certains caractères pouvant appartenir à plusieurs catégories en même temps. Deux combinaisons standards sont d'ailleurs définies, alnum, qui caractérise les caractères alphanumériques, et graph, qui représente tous les caractères alphanumériques et de ponctuation. Les autres constantes permettent de caractériser les caractères selon leur nature et leur signification est en général claire. La seule constante qui dont l'interprétation n'est pas immédiate est la constante xdigit, qui identifie tous les caractères pouvant servir de chiffre dans la notation des nombres hexadécimaux. Cela comprend les chiffres normaux et les lettres 'A' à 'F'.

    La classe template ctype quant à elle est déclarée comme suit dans l'en-tête locale :

    template <class charT>
    class ctype : public locale::facet, public ctype_base
    {
    public:
    // Les types de données :
    typedef charT char_type;

    // Le constructeur :
    explicit ctype(size_t refs = 0);

    // Les méthode de classification :
    bool is(mask m, charT c) const;
    const charT *is(const charT *premier, const charT *dernier,
    mask *vecteur) const;
    const charT *scan_is(mask m,
    const charT *premier, const charT *dernier) const;
    const charT *scan_not(mask m,
    const charT *premier, const charT *dernier) const;
    charT toupper(charT c) const;
    const charT *toupper(const charT *premier, const charT *dernier) const;
    charT tolower(charT c) const;
    const charT *tolower(const charT *premier, const charT *dernier) const;
    charT widen(char c) const;
    const charT *widen(const char *premier, const char *dernier,
    charT *destination) const;
    char narrow(charT c, char defaut) const;
    const char *narrow(const charT *premier, const charT *dernier,
    char defaut, char *destination) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Comme pour toutes les facettes standards, les méthodes publiques délèguent leur travail à des méthodes virtuelles déclarées en zone protégée dont le nom est celui de la méthode publique préfixé par la chaîne de caractères « do_ ». Ces méthodes peuvent être redéfinies par les classes dérivées de la facette ctype et font donc partie de l'interface des facettes standards. Cependant, elles ne sont pas représentées dans la déclaration donnée ci-dessus par souci de simplicité. Leur sémantique est exactement la même que celle des méthodes publiques correspondantes. Nous verrons dans la section 16.3.2 la manière de procéder pour redéfinir certaines des méthodes des facettes standards.

    Les méthodes scan_is et scan_not permettent de rechercher un caractère selon un critère particulier dans un tableau de caractères. La méthode scan_is recherche le premier caractère qui est du type indiqué par son paramètre m, et la méthode scan_not le premier caractère qui n'est pas de ce type. Ces deux méthodes prennent en paramètre un pointeur sur le premier caractère du tableau dans lequel la recherche doit s'effectuer et le pointeur suivant l'emplacement du dernier caractère de ce tableau. Elles renvoient toutes les deux un pointeur référençant le caractère trouvé, ou le pointeur de fin si aucun caractère ne vérifie le critère spécifié.

    Les autres méthodes de la facette ctype sont fournies sous deux versions. La première permet d'effectuer une opération sur un caractère unique et la deuxième permet de reproduire cette opération sur une séquence de caractères consécutifs. Dans ce dernier cas, les caractères sur lesquels l'opération peut être effectuée sont spécifiés à l'aide de deux pointeurs, l'un sur le premier caractère et l'autre sur le caractère suivant le dernier caractère de la séquence, comme il est d'usage de le faire dans tous les algorithmes de la bibliothèque standard.

    Les deux méthodes is permettent donc respectivement de déterminer si un caractère est du type indiqué par le paramètre m ou non, ou d'obtenir la suite des descriptions de chaque caractère dans le tableau de valeur de type mask pointé par le paramètre vecteur. De même, les méthodes toupper et tolower permettent respectivement de convertir un caractère unique ou tous les caractères d'un tableau en majuscule ou en minuscule. La méthode widen permet de transtyper un caractère ou tous les caractères d'un tableau de type char en caractères du type par lequel la classe ctype est paramétrée. Enfin, les méthodes narrow permettent de réaliser l'opération inverse, ce qui peut provoquer une perte de données puisque le type char est le plus petit des types de caractères qui puisse exister. Il est donc possible que le transtypage ne puisse se faire, dans ce cas, les méthodes narrow utilisent la valeur par défaut spécifiée par le paramètre defaut.

    Exemple 16-2. Conversion d'une wstring en string

    #include <iostream>
    #include <string>
    #include <locale>

    using namespace std;

    int main(void)
    {
    // Fixe la locale globale aux préférences de l'utilisateur :
    locale::global(locale(""));
    // Lit une chaîne de caractères larges :
    wstring S;
    wcin >> S;
    // Récupère la facette ctype<wchar_t> de la locale courante :
    const ctype<wchar_t> &f =
    use_facet<ctype<wchar_t> >(locale());
    // Construit un tampon pour recevoir le résultat de la conversion :
    size_t l = S.length() + 1;
    char *tampon = new char[l];
    // Effectue la conversion :
    f.narrow(S.c_str(), S.c_str() + l, 'E', tampon);
    // Affiche le résultat :
    cout << tampon << endl;
    delete[] tampon;
    return 0;
    }

    Note : Les conversions effectuées par les méthodes narrow et widen ne travaillent qu'avec les représentations de caractères classiques du langage C++. Cela signifie que les caractères sont tous représentés par une unique valeur de type char ou wchar_t (ces méthodes n'utilisent donc pas de représentation des caractères basées sur des séquences de caractères de longueurs variables). La méthode narrow de l'exemple précédent écrit donc autant de caractères dans le tampon destination qu'il y en a dans la chaîne à convertir.

    Vous constaterez que l'utilisation de la méthode is pour déterminer la nature des caractères peut être relativement fastidieuse, car il faut récupérer la facette ctype, déterminer la valeur du masque à utiliser, puis appeler la méthode. La bibliothèque standard définit donc un certain nombre de fonctions globales utilitaires dans l'en-tête locale :

    template <class charT> bool isspace (charT c, const locale &l) const;
    template <class charT> bool isprint (charT c, const locale &l) const;
    template <class charT> bool iscntrl (charT c, const locale &l) const;
    template <class charT> bool isupper (charT c, const locale &l) const;
    template <class charT> bool islower (charT c, const locale &l) const;
    template <class charT> bool isalpha (charT c, const locale &l) const;
    template <class charT> bool isdigit (charT c, const locale &l) const;
    template <class charT> bool ispunct (charT c, const locale &l) const;
    template <class charT> bool isxdigit(charT c, const locale &l) const;
    template <class charT> bool isalnum (charT c, const locale &l) const;
    template <class charT> bool isgraph (charT c, const locale &l) const;
    template <class charT> charT toupper(charT c, const locale &l) const;
    template <class charT> charT tolower(charT c, const locale &l) const;

    L'utilisation de ces fonctions ne doit pas poser de problème particulier. Elles prennent toutes en premier paramètre le caractère à caractériser et en deuxième paramètre la locale dont la facette ctype doit être utilisée pour réaliser cette caractérisation. Chaque fonction permet de tester le caractère pour l'appartenance à l'une des catégories de caractères définies dans la classe de base ctype_base. Notez cependant que si un grand nombre de caractères doivent être caractérisés pour une même locale, il est plus performant d'obtenir la facette ctype de cette locale une bonne fois pour toutes et d'effectuer les appels à la méthode is en conséquence.

    La classe ctype étant une classe template, elle peut être utilisée pour n'importe quel type de caractère a priori. Toutefois, il est évident que cette classe peut être optimisée pour les types de caractère simples, et tout particulièrement pour le type char, parce qu'il ne peut pas prendre plus de 256 valeurs différentes. La bibliothèque standard définit donc une spécialisation totale de la classe template ctype pour le type char. L'implémentation de cette spécialisation se base sur un tableau de valeurs de type mask indexée par les valeurs que peuvent prendre les variables de type char. Ce tableau permet donc de déterminer rapidement les caractéristiques de chaque caractère existant. Le constructeur de cette spécialisation diffère légèrement du constructeur de sa classe template car il peut prendre en paramètre un pointeur sur ce tableau de valeurs et un booléen indiquant si ce tableau doit être détruit automatiquement par la facette lorsqu'elle est elle-même détruite ou non. Ce constructeur prend également en troisième paramètre une valeur de type entier indiquant, comme pour toutes les facettes standards, si la locale doit prendre en charge la gestion de la durée de vie de la facette ou non. Les autres méthodes de cette spécialisation sont identiques aux méthodes de la classe template de base et ne seront donc pas décrites ici.

    16.2.2.2. La facette codecvt

    La facette codecvt permet de réaliser les opérations de conversion d'un mode de représentation des caractères à un autre. En général, en informatique, les caractères sont codés par des nombres. Le type de ces nombres, ainsi que la manière de les utiliser, peut varier grandement d'une représentation à une autre, et les conversions peuvent ne pas se faire simplement. Par exemple, certaines représentations codent chaque caractère avec une valeur unique du type de caractère utilisé, mais d'autres codent les caractères sur des séquences de longueur variable. On ne peut dans ce cas bien entendu pas convertir directement une représentation en une autre, car l'interprétation que l'on peut faire des nombres représentant les caractères dépend du contexte déterminé par les nombres déjà lus. Les opérations de conversion ne sont donc pas toujours directes.

    De plus, dans certains encodages à taille variable, l'interprétation des caractères peut dépendre des caractères déjà convertis. La facette codecvt maintient donc un état pendant les conversions qu'elle effectue, état qui lui permet de reprendre la conversion d'une séquence de caractères dans le cas de conversions réalisées en plusieurs passes. Bien entendu, tous les encodages ne nécessitent pas forcément le maintien d'un tel état. Cependant, certains l'exigent et il faut donc toujours le prendre en compte dans les opérations de conversion si l'on souhaite que le programme soit portable. Pour les séquences de caractères à encodage variable utilisant le type de caractère de base char, le type de la variable d'état permettant de stocker l'état courant du convertisseur est le type mbstate_t. D'autres types peuvent être utilisés pour les séquences basées sur des types de caractères différents du type char, mais en général, tous les encodages à taille variable se basent sur ce type. Quoi qu'il en soit, la classe codecvt définit un type de donnée capable de stocker l'état d'une conversion partielle. Ce type est le type state_type, qui pourra donc toujours être récupéré dans la classe codecvt. La variable d'état du convertisseur devra être systématiquement fournie aux méthodes de conversion de la facette codecvt et devra bien entendu être initialisée à sa valeur par défaut au début de chaque nouvelle conversion.

    Note : La facette codecvt permet de réaliser les conversions d'une représentation des caractères à une autre, mais n'a pas pour but de changer l'encodage des caractères, c'est-à-dire l'association qui est faite entre les séquences de nombres et les caractères. Cela signifie que la facette codecvt permet par exemple de convertir des chaînes de caractères larges wchar_t en séquences de longueurs variables de caractères de type char, mais elle ne permet pas de passer d'une page de codes à une autre.

    La facette codecvt dérive d'une classe de base nommée codecvt_base. Cette classe définit les différents résultats que peuvent avoir les opérations de conversion. Elle est déclarée comme suit dans l'en-tête locale :

    class codecvt_base
    {
    public:
    enum result
    {
    ok, partial, error, noconv
    };
    };

    Comme vous pouvez le constater, une conversion peut se réaliser complètement (code de résultat ok), partiellement par manque de place dans la séquence destination ou par manque de données en entrées (code partial), ou pas du tout, soit en raison d'une erreur de conversion (code d'erreur error), soit parce qu'aucune conversion n'est nécessaire (code de résultat noconv).

    La classe template codecvt elle-même est définie comme suit dans l'en-tête locale :

    template <class internT, class externT, class stateT>
    class codecvt : public locale::facet, public codecvt_base
    {
    public:
    // Les types de données :
    typedef internT intern_type;
    typedef externT extern_type;
    typedef stateT state_type;

    // Le constructeur :
    explicit codecvt(size_t refs=0);

    // Les fonctions de conversion :
    result out(stateT &etat, const internT *premier,
    const internT *dernier, const internT *&suiv_source,
    externT *dernier, externT *limite, externT *&suiv_dest) const;
    result in(stateT &etat, const externT *premier,
    const externT *dernier, const externT *&suiv_source,
    internT *dernier, internT *limite, internT *&suiv_dest) const;
    result unshift(stateT &etat,
    externT *dernier, externT *limite, externT *&suiv_dest) const;
    int length(const stateT &etat,
    const externT *premier, const externT *dernier, size_t max) const;
    int max_length() const throw();
    int encoding() const throw();
    bool always_noconv() const throw();

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Cette classe template est paramétrée par le type de caractère interne à la classe codecvt, par un deuxième type de caractère qui sera par la suite dénommé type externe, et par le type des variables destinées à recevoir l'état courant d'une conversion. Les implémentations de la bibliothèque standard doivent obligatoirement instancier cette classe template pour les types char et wchar_t. Le type de gestion de l'état des conversions utilisé est alors le type prédéfini mbstate_t, qui permet de conserver l'état des conversions entre le type natif wchar_t et les séquences de caractères simples à taille variable. Ainsi, vous pourrez toujours utiliser les instances codecvt<wchar_t, char, mbstate_t> et codecvt<char, char, mbstate_t> de la facette codecvt dans vos programmes. Si vous désirez réaliser des conversions pour d'autres types de caractères, vous devrez fournir vous-même des spécialisations de la facette codecvt.

    Les méthodes in et out permettent respectivement, comme leurs signatures l'indiquent, de réaliser les conversions entre les types interne et externe et vice versa. Elles prennent toutes deux sept paramètres. Le premier paramètre est une référence sur la variable d'état qui devra être fournie à chaque appel lors de conversions successives d'un même flux de données. Cette variable est destinée à recevoir l'état courant de la conversion et permettra aux appels suivants de convertir correctement les caractères suivants du flux d'entrée. Les deux paramètres suivants permettent de spécifier la séquence de caractères à convertir. Ils doivent contenir le pointeur sur le début de la séquence et le pointeur sur le caractère suivant le dernier caractère de la séquence. Le quatrième paramètre est un paramètre de retour, la fonction lui affectera la valeur du pointeur où la conversion s'est arrêtée. Une conversion peut s'arrêter à cause d'une erreur ou tout simplement parce que le tampon destination ne contient pas assez de place pour accueillir un caractère de plus. Ce pointeur pourra être utilisé dans un appel ultérieur comme pointeur de départ avec la valeur de la variable d'état à l'issue de la conversion pour effectuer la suite de cette conversion. Enfin, les trois derniers paramètres spécifient le tampon destination dans lequel la séquence convertie doit être écrite. Ils permettent d'indiquer le pointeur de début de ce tampon, le pointeur suivant le dernier emplacement utilisable, et un pointeur de retour qui indiquera la dernière position écrite par l'opération de conversion. Ces deux méthodes renvoient une des constantes de l'énumération result définie dans la classe de base codecvt_base pour indiquer comment la conversion s'est effectuée. Si aucune conversion n'est nécessaire, les pointeurs sur les caractères suivants sont initialisés à la valeur des pointeurs de début de séquence et aucune écriture n'a lieu dans le tampon destination.

    Exemple 16-3. Conversion d'une chaîne de caractères larges en chaîne à encodage variable

    #include <iostream>
    #include <string>
    #include <locale>

    using namespace std;

    int main(void)
    {
    // Fixe la locale globale :
    locale::global(locale(""));
    // Lit une ligne :
    wstring S;
    getline(wcin, S);
    // Récupère la facette de conversion vers wchar_t :
    const codecvt<wchar_t, char, mbstate_t> &f =
    use_facet<codecvt<wchar_t, char, mbstate_t> >(locale());
    // Effectue la conversion :
    const wchar_t *premier = S.c_str();
    const wchar_t *dernier = premier + S.length();
    const wchar_t *suivant = premier;
    string s;
    char tampon[10];
    char *fincvt = tampon;
    codecvt_base::result r;
    mbstate_t etat = mbstate_t();
    while (premier != dernier)
    {
    // Convertit un morceau de la chaîne :
    r = f.out(etat, premier, dernier, suivant,
    tampon, tampon+10, fincvt);
    // Vérifie les erreurs possibles :
    if (r == codecvt_base::ok || r == codecvt_base::partial)
    cout << "." << flush;
    else if (r == codecvt_base::noconv)
    {
    cout << "conversion non nécessaire" << endl;
    break;
    }
    else if (r == codecvt_base::error)
    {
    cout << "erreur" << endl;
    cout << suivant - premier << endl;
    cout << fincvt - tampon << endl;
    break ;
    }
    // Récupère le résultat et prépare la conversion suivante :
    s.append(tampon, fincvt - tampon);
    premier = suivant;
    }
    cout << endl;
    // Affiche le résultat :
    cout << s << endl;
    return 0;
    }

    Note : Si l'on désire effectuer une simple conversion d'une chaîne de caractères de type wchar_t en chaîne de caractères C classique, on cherchera plutôt à utiliser la méthode narrow de la facette ctype présentée dans la section précédente. En effet, la facette codecvt utilise, a priori, une séquence de caractères avec un encodage à taille variable, ce qui ne correspond pas à la représentation des chaînes de caractères C classiques, pour lesquelles chaque valeur de type char représente un caractère.

    Il est possible de compléter une séquence de caractères à encodage variable de telle sorte que la variable d'état du convertisseur soit réinitialisée. Cela permet de terminer une chaîne de caractères partiellement convertie, ce qui en pratique revient à compléter la séquence de caractères avec les données qui représenteront le caractère nul terminal. Cette opération peut être réalisée à l'aide de la méthode unshift de la facette codecvt. Cette méthode prend en paramètre une référence sur la variable d'état du convertisseur, ainsi que les pointeurs de début et de fin du tampon dans lequel les valeurs à ajouter sont écrites. Le dernier paramètre de la méthode unshift est une référence sur un pointeur qui recevra l'adresse suivant celle la dernière valeur écrite par la méthode si l'opération se déroule correctement.

    Il va de soi que la détermination de la longueur d'une chaîne de caractères dont les caractères ont une représentation à taille variable n'est pas simple. La facette codecvt comporte donc une méthode length permettant de calculer, en nombre de caractères de type intern_type, la longueur d'une séquence de caractères de type extern_type. Cette méthode prend en paramètre la variable d'état du convertisseur ainsi que les pointeurs spécifiant la séquence de caractères dont la longueur doit être calculée. Le dernier paramètre est la valeur maximale que la fonction peut retourner. Elle permet de limiter la détermination de la longueur de la séquence source à une borne maximale, par exemple la taille d'un tampon destination. La valeur retournée est bien entendu la longueur de cette séquence ou, autrement dit, le nombre de valeurs de type intern_type nécessaires pour stocker le résultat de la conversion que la méthode in ferait avec les mêmes paramètres. D'autre part, il est possible de déterminer le nombre maximal de valeurs de type intern_type nécessaires pour représenter un unique caractère représenté par une séquence de caractères de type extern_type. Pour cela, il suffit d'appeler la méthode max_length de la facette codecvt.

    Exemple 16-4. Détermination de la longueur d'une chaîne de caractères à encodage variable

    #include <iostream>
    #include <string>
    #include <locale>
    #include <limits>

    using namespace std;

    int main(void)
    {
    // Fixe la locale globale :
    locale::global(locale(""));
    // Lit une ligne :
    string s;
    getline(cin, s);
    // Récupère la facette de conversion vers wchar_t :
    const codecvt<wchar_t, char, mbstate_t> &f =
    use_facet<codecvt<wchar_t, char, mbstate_t> >(locale());
    // Affiche la longueur de la chaîne d'entrée :
    int l1 = s.length();
    // Calcule la longueur de la ligne en wchar_t :
    mbstate_t etat = mbstate_t();
    int l2 = f.length(etat, s.c_str(), s.c_str() + l1,
    numeric_limits<size_t>::max());
    // Affiche les deux longueurs :
    cout << l1 << endl;
    cout << l2 << endl;
    return 0;
    }

    Comme on l'a déjà indiqué ci-dessus, toutes les représentations des caractères ne sont pas à taille variable et toutes les représentations ne nécessitent pas forcément l'utilisation d'une variable d'état de type state_type. Vous pouvez déterminer dynamiquement si le mode de représentation des caractères du type intern_type utilise un encodage à taille variable ou non à l'aide de la méthode encoding. Cette méthode renvoie -1 si la représentation des caractères de type extern_type dépend de l'état du convertisseur, ou le nombre de caractères de type extern_type nécessaires au codage d'un caractère de type intern_type si ce nombre est constant. Si la valeur renvoyée est 0, ce nombre n'est pas constant, mais, contrairement à ce qui se passe lorsque la valeur renvoyée est -1, ce nombre ne dépend pas de la valeur de la variable d'état du convertisseur.

    Enfin, certains modes de représentation des caractères sont compatibles, voire franchement identiques. Dans ce cas, jamais aucune conversion n'est réalisée, et les méthodes in et out renvoient toujours noconv. C'est par exemple le cas de la spécialisation codecvt<char, char, mbstate_t> de la facette codecvt. Vous pouvez déterminer si une facette effectuera des conversions ou non en appelant la méthode always_noconv. Elle retourne true si jamais aucune conversion ne se fera et false sinon.

    16.2.3. Les facettes de comparaison de chaînes

    Les chaînes de caractères sont généralement classées par ordre alphabétique, ou, plus précisément, dans l'ordre lexicographique. L'ordre lexicographique est l'ordre défini par la séquence des symboles lexicaux utilisés (c'est-à-dire les symboles utilisés pour former les mots du langage, donc, en pratique, les lettres, les nombres, la ponctuation, etc.). Cet ordre est celui qui est défini par la comparaison successive des caractères des deux chaînes à comparer, le premier couple de caractères différents permettant de donner un jugement de classement. Ainsi, les chaînes les plus petites au sens de l'ordre lexicographique sont les chaînes qui commencent par les premiers symboles du lexique utilisé. Cette manière de procéder suppose bien entendu que les symboles utilisés pour former les mots du lexique sont classés dans un ordre correct. Par exemple, il faut que la lettre 'a' apparaisse avant la lettre 'b', qui elle-même doit apparaître avant la lettre 'c', etc.

    Malheureusement, cela n'est pas si simple, car cet ordre n'est généralement pas celui utilisé par les pages de codes d'une part, et il existe toujours des symboles spéciaux dont la classification nécessite un traitement spécial d'autre part. Par exemple, les caractères accentués sont généralement placés en fin de page de code et apparaissent donc à la fin de l'ordre lexicographique, ce qui perturbe automatiquement le classement des chaînes de caractères contenant des accents. De même, certaines lettres sont en réalité des compositions de lettres et doivent être prises en compte en tant que telles dans les opérations de classement. Par exemple, la lettre 'æ' doit être interprétée comme un 'a' suivi d'un 'e'. Et que dire du cas particulier des majuscules et des minuscules ?

    Comme vous pouvez le constater, il n'est pas possible de se baser uniquement sur l'ordre des caractères dans leur page de code pour effectuer les opérations de classement de chaînes de caractères. De plus, il va de soi que l'ordre utilisé pour classer les symboles lexicographiques dépend de ces symboles et donc de la locale utilisé. La bibliothèque standard fournit donc une facette prenant en compte tous ces paramètres : la classe template collate.

    Le principe de fonctionnement de la facette collate est de transformer les chaînes de caractères utilisant les conventions de la locale à laquelle la facette appartient en une chaîne de caractères indépendante de la locale, comprenant éventuellement des codes de contrôle spéciaux pour les caractères spécifiques à cette locale. Les chaînes de caractères ainsi transformées peuvent alors être comparées entre elles directement, avec les méthodes de comparaison classique de chaînes de caractères qui utilisent l'ordre lexicographique du jeu de caractères du langage C. La transformation est effectuée de telle manière que cette comparaison produit le même résultat que la comparaison tenant compte de la locale des chaînes de caractères non transformées.

    La facette collate est déclarée comme suit dans l'en-tête locale :

    template <class charT>
    class collate : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit collate(size_t refs = 0);

    // Les méthodes de comparaison de chaînes :
    string_type transform(const charT *debut, const charT *fin) const;
    int compare(const charT *deb_premier, const charT *fin_premier,
    const charT *deb_deuxieme, const charT *fin_deuxieme) const;
    long hash(const charT *debut, const charT *fin) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    La méthode transform est la méthode fondamentale de la facette collate. C'est cette méthode qui permet d'obtenir la chaîne de caractères transformée. Elle prend en paramètre le pointeur sur le début de la chaîne de caractères à transformer et le pointeur sur le caractère suivant le dernier caractère de cette chaîne. Elle retourne une basic_string contenant la chaîne transformée, sur laquelle les opérations de comparaison classiques pourront être appliquées.

    Il est possible d'effectuer directement la comparaison entre deux chaînes de caractères, sans avoir à récupérer les chaînes de caractères transformées. Cela peut être réalisé grâce à la méthode compare, qui prend en paramètre les pointeurs de début et de fin des deux chaînes de caractères à comparer et qui renvoie un entier indiquant le résultat de la comparaison. Cet entier est négatif si la première chaîne est inférieure à la deuxième, positif si elle est supérieure, et nul si les deux chaînes sont équivalentes.

    Exemple 16-5. Comparaison de chaînes de caractères localisées

    #include <iostream>
    #include <string>
    #include <locale>

    using namespace std;

    int main(void)
    {
    // Fixe la locale globale :
    locale::global(locale(""));
    // Lit deux lignes en entrée :
    cout << "Entrez la première ligne :" << endl;
    string s1;
    getline(cin, s1);
    cout << "Entrez la deuxième ligne :" << endl;
    string s2;
    getline(cin, s2);
    // Récupère la facette de comparaison de chaînes :
    const collate<char> &f =
    use_facet<collate<char> >(locale());
    // Compare les deux chaînes :
    int res = f.compare(
    s1.c_str(), s1.c_str() + s1.length(),
    s2.c_str(), s2.c_str() + s2.length());
    if (res < 0)
    {
    cout << "\"" << s1 << "\" est avant \"" <<
    s2 << "\"." << endl;
    }
    else if (res > 0)
    {
    cout << "\"" << s1 << "\" est après \"" <<
    s2 << "\"." << endl;
    }
    else
    {
    cout << "\"" << s1 << "\" est égale à \"" <<
    s2 << "\"." << endl;
    } return 0;
    }

    Note : La méthode compare est très pratique pour comparer deux chaînes de caractères de manière ponctuelle. Cependant, on lui préférera la méthode transform si un grand nombre de comparaisons doit être effectué. En effet, il est plus simple de transformer toutes les chaînes de caractères une bonne fois pour toutes et de travailler ensuite directement sur les chaînes transformées. Ce n'est que lorsque les opérations de comparaison auront été terminées que l'on pourra revenir sur les chaînes de caractères initiales. On évite ainsi de faire des transformation à répétition des chaînes à comparer et on gagne ainsi beaucoup de temps. Bien entendu, cela nécessite de conserver l'association entre les chaînes de caractères transformées et les chaînes de caractères initiales, et donc de doubler la consommation mémoire du programme due au chaînes de caractères pendant le traitement de ces chaînes.

    Enfin, il est courant de chercher à déterminer une clef pour chaque chaîne de caractères. Cette clef peut être utilisée pour effectuer une recherche rapide des chaînes de caractères. La méthode hash de la facette collate permet de calculer une telle clef, en garantissant que deux chaînes de caractères identiques au sens de la méthode compare auront la même valeur de clef. On notera cependant que cette clef n'est pas unique, deux chaînes de caractères peuvent avoir deux valeurs de clefs identiques même si la méthode compare renvoie une valeur non nulle. Cependant, ce cas est extrêmement rare, et permet d'utiliser malgré tout des algorithmes de recherche rapide. La seule chose à laquelle il faut faire attention est que ces algorithmes doivent pouvoir supporter les clefs multiples.

    Note : Les clefs à probabilité de recouvrement faible comme celle retournée par la méhtode hash sont généralement utilisées dans les structures de données appelées tables de hachage, ce qui explique le nom donné à cette méthode. Les tables de hachage sont en réalité des tableaux de listes chaînées indexés par la clef de hachage (si la valeur de la clef dépasse la taille du tableau, elle est ramenée dans les limites de celui-ci par une opération de réduction). Ce sont des structures permettant de rechercher rapidement des valeurs pour lesquelles une fonction de hachage simple existe. Cependant, elles se comportent moins bien que les arbres binaires lorsque le nombre d'éléments augmente (quelques milliers). On leur préférera donc généralement les associations de la bibliothèque standard, comme les map et multimap par exemple. Vous pouvez consulter la bibliographie si vous désirez obtenir plus de renseignements sur les tables de hachage et les structures de données en général. Les associations et les conteneurs de la bibliothèque standard seront décrites dans le chapitre 17.

    16.2.4. Les facettes de gestion des nombres

    Les opérations de formatage et les opérations d'interprétation des données numériques dépendent bien entendu des conventions nationales de la locale incluse dans les flux qui effectuent ces opérations. En réalité, ces opérations ne sont pas prises en charge directement par les flux, mais plutôt par les facettes de gestion des nombres, qui regroupent toutes les opérations propres aux conventions nationales.

    La bibliothèque standard définit en tout trois facettes qui interviennent dans les opérations de formatage : une facette utilitaire, qui contient les paramètres spécifiques à la locale, et deux facettes dédiées respectivement aux opérations de lecture et aux opérations d'écriture des nombres.

    16.2.4.1. La facette num_punct

    La facette qui regroupe tous les paramètres de la locale est la facette num_punct. Elle est déclarée comme suit dans l'en-tête locale :

    template <class charT>
    class numpunct : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit numpunct(size_t refs = 0);

    // Les méthodes de lecture des options de formatage des nombres :
    char_type decimal_point() const;
    char_type thousands_sep() const;
    string grouping() const;
    string_type truename() const;
    string_type falsename() const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    La méthode decimal_point permet d'obtenir le caractère qui doit être utilisé pour séparer le chiffre des unités des chiffres après la virgule lors des opérations de formatage des nombres à virgule. La valeur par défaut est le caractère '.', mais en France, le caractère utilisé est la virgule (caractère ','). De même, la méthode thousands_sep permet de déterminer le caractère qui est utilisé pour séparer les groupes de chiffres lors de l'écriture des grands nombres. La valeur par défaut renvoyée par cette fonction est le caractère virgule (caractère ','), mais dans les locales françaises, on utilise généralement un espace (caractère ' '). Enfin, la méthode grouping permet de déterminer les emplacements où ces séparateurs doivent être introduits. La chaîne de caractères renvoyée détermine le nombre de chiffres de chaque groupe de chiffres. Le nombre de chiffres du premier groupe est ainsi stocké dans le premier caractère de la chaîne de caractères renvoyée par la méthode grouping, celui du deuxième groupe est stocké dans le deuxième caractère, et ainsi de suite. Le dernier nombre ainsi obtenu dans cette chaîne de caractères est ensuite utilisé pour tous les groupes de chiffres suivants, ce qui évite d'avoir à définir une chaîne de caractères arbitrairement longue. Un nombre de chiffres nul indique que le mécanisme de groupage des chiffres des grands nombres est désactivé. Les facettes de la plupart des locales renvoient la valeur "\03", ce qui permet de grouper les chiffres par paquets de trois (milliers, millions, milliards, etc.).

    Note : Remarquez que les valeurs stockées dans la chaîne de caractères renvoyée par la méthode grouping sont des valeurs numériques et non des chiffres formatés dans la chaîne de caractères. Ainsi, la valeur par défaut renvoyée est bien "\03" et non "3".

    Les méthodes truename et falsename quant à elles permettent aux facettes de formatage d'obtenir les chaînes de caractères qui représentent les valeurs true et false des booléens. Ce sont ces chaînes de caractères qui sont utilisées lorsque l'option de formatage boolalpha a été activée dans les flux d'entrée / sortie. Les valeurs retournées par ces méthodes sont, par défaut, les mots anglais true et false. Il est concevable dans d'autres locales, cependant, d'avoir des noms différents pour ces deux valeurs. Nous verrons dans la section 16.3.2 la manière de procéder pour redéfinir ces méthodes et construire ainsi une locale personnalisée et francisée.

    Note : Bien entendu, les facettes d'écriture et de lecture des nombres utilisent également les options de formatage qui sont définis au niveau des flux d'entrée / sortie. Pour cela, les opérations d'entrée / sortie reçoivent en paramètre une référence sur le flux contenant ces options.

    16.2.4.2. La facette d'écriture des nombres

    L'écriture et le formatage des nombres sont pris en charge par la facette num_put. Cette facette est déclarée comme suit dans l'en-tête locale :

    template <class charT,
    class OutputIterator = ostreambuf_iterator<charT> >
    class num_put : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef OutputIterator iter_type;

    // Le constructeur :
    explicit num_put(size_t refs = 0);

    // Les méthodes d'écriture des nombres :
    iter_type put(iter_type s, ios_base &f, char_type remplissage, bool v) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage, long v) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage,
    unsigned long v) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage,
    double v) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage,
    long double v) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage,
    void *v) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Comme vous pouvez le constater, cette facette dispose d'une surcharge de la méthode put pour chacun des types de base du langage. Ces surcharges prennent en paramètre un itérateur d'écriture sur le flux de sortie sur lequel les données formatées devront être écrites, une référence sur le flux de sortie contenant les options de formatage à utiliser lors du formatage des nombres, le caractère de remplissage à utiliser, et bien entendu la valeur à écrire.

    En général, ces méthodes sont appelées au sein des opérateurs d'insertion operator<< pour chaque type de donnée existant. De plus, le flux de sortie sur lequel les écritures doivent être effectuées est le même que le flux servant à spécifier les options de formatage, si bien que l'appel aux méthodes put est extrêmement simplifié. Nous verrons plus en détail la manière d'appeler ces méthodes dans la section 16.3.1, lorsque nous écrirons une nouvelle facette pour un nouveau type de donnée.

    16.2.4.3. La facette de lecture des nombres

    Les opérations de lecture des nombres à partir d'un flux de données sont prises en charge par la facette num_get. Cette facette est déclarée comme suit dans l'en-tête locale :

    template <class charT,
    class InputIterator = istreambuf_iterator<charT> >
    class num_get : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef InputIterator iter_type;

    // Le constructeur :
    explicit num_get(size_t refs = 0);

    // Les méthodes de lecture des nombres :
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, bool &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, long &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, unsigned short &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, unsigned int &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, unsigned long &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, float &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, double &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, long double &v) const;
    iter_type get(iter_type in, iter_type end, ios_base &,
    ios_base::iostate &err, void *&v) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Comme vous pouvez le constater, cette facette ressemble beaucoup à la facette num_put. Il existe en effet une surcharge de la méthode get pour chaque type de base du langage. Ces méthodes sont capables d'effectuer la lecture des données de ces types à partir du flux d'entrée, en tenant compte des paramètres des locales et des options de formatage du flux. Ces méthodes prennent en paramètre un itérateur de flux d'entrée, une valeur limite de cet itérateur au-delà de laquelle la lecture du flux ne se fera pas, la référence sur le flux d'entrée contenant les options de formatage et deux paramètres de retour. Le premier paramètre recevra un code d'erreur de type iostate qui pourra être positionné dans le flux d'entrée pour signaler l'erreur. Le deuxième est une référence sur la variable devant accueillir la valeur lue. Si une erreur se produit, cette variable n'est pas modifiée.

    Les méthodes get sont généralement utilisées par les opérateurs d'extraction operator>> des flux d'entrée / sortie pour les types de données de base du langage. En général, ces opérateurs récupèrent la locale incluse dans le flux d'entrée sur lequel ils travaillent et utilisent la facette num_get de cette locale. Ils appellent alors la méthode get permettant de lire la donnée qu'ils doivent extraire, en fournissant ce même flux en paramètre. Ils testent ensuite la variable d'état retournée par la méthode get et, si une erreur s'est produite, modifient l'état du flux d'entrée en conséquence. Cette dernière opération peut bien entendu provoquer le lancement d'une exception, selon le masque d'exceptions utilisé pour le flux.

    16.2.5. Les facettes de gestion des monnaies

    La bibliothèque standard ne définit pas de type de donnée dédiés à la représentation des montants. Elle suppose en effet que les montants sont stockés dans des nombres à virgule flottante dans la plupart des programmes ou, pour les programmes qui désirent s'affranchir des erreurs d'arrondis inévitables lors de l'utilisation de flottants, sous forme textuelle dans des chaînes de caractères. En revanche, la bibliothèque standard fournit, tout comme pour les types standards, deux facettes localisées prenant en compte la lecture et l'écriture des montants. Ces facettes se basent également sur une troisième facette qui regroupe tous les paramètres spécifiques aux conventions nationales.

    Note : En réalité, les seuls types capables de représenter correctement les montants en informatique sont les entiers et les nombres à virgule fixe codés sur les entiers. En effet, les types intégraux sont les seuls types qui ne soulèvent pas de problème de représentation des nombres (à condition qu'il n'y ait pas de débordements bien entendu) et les nombres à virgule fixe sont particulièrement adaptés aux montants, car en général le nombre de chiffres significatifs après la virgule est fixé pour une monnaie donnée. Les nombres à virgule flottante ne permettent pas de représenter des valeurs avec précision et introduisent des erreurs incontrôlables dans les calculs et dans les arrondis. Les chaînes de caractères quant à elles souffrent de leur lourdeur et, dans la plupart des cas, de la nécessité de passer par des nombres à virgule flottantes pour interpréter leur valeur. Les facettes présentées dans cette section sont donc d'une utilité réduite pour les programmes qui cherchent à obtenir des résultats rigoureux et précis, et qui ne tolèrent pas les erreurs de représentation et les erreurs d'arrondis.

    Toutes les facettes de gestion des montants sont des classes template. Cependant, contrairement aux autres facettes, ces facettes disposent d'un autre paramètre template que le type de caractère sur lequel elles travaillent. Ce paramètre est un paramètre de type booléen qui permet, selon sa valeur, de spécifier si les facettes doivent travailler avec la représentation internationale des montants ou non. Il existe en effet une représentation universelle des montants qui, entre autres particularités, utilise les codes internationaux de monnaie (« USD » pour le dollar américain, « CAN » pour le dollar canadien, « EUR » pour l'euro, etc.).

    Comme pour les facettes de gestion des nombres, les facettes prenant en charge les monnaies sont au nombre de trois. Une de ces trois facettes permet d'obtenir des informations sur la monnaie de la locale et les deux autres réalisent respectivement les opérations d'écriture et de lecture sur un flux.

    16.2.5.1. La facette money_punct

    La facette moneypunct est la facette permettant aux deux facettes d'écriture et de lecture des montants d'obtenir les informations relatives à la monnaie de leur locale. Cette facette est déclarée comme suit dans l'en-tête locale :

    template <class charT, bool International = false>
    class moneypunct : public locale::facet, public money_base
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit moneypunct(size_t refs = 0);

    // Les méthodes de lecture des options de formatage des montants :
    charT decimal_point() const;
    charT thousands_sep() const;
    string grouping() const;
    int frac_digits() const;
    string_type curr_symbol() const;
    pattern pos_format() const;
    pattern neg_format() const;
    string_type positive_sign() const;
    string_type negative_sign() const;
    static const bool intl = International;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Comme vous pouvez le constater, cette facette dispose de méthodes permettant de récupérer les divers symboles qui sont utilisés pour écrire les montants de la monnaie qu'elle décrit. Ainsi, la méthode decimal_point renvoie le caractère qui doit être utilisé en tant que séparateur du chiffre des unités de la partie fractionnaire des montants, si celle-ci doit être représentée. De même, la méthode thousands_sep renvoie le caractère qui doit être utilisé pour séparer les groupes de chiffres pour les grands montants, et la méthode grouping renvoie une chaîne contenant, dans chacun de ses caractères, le nombre de chiffres de chaque groupe. Ces méthodes sont donc semblables aux méthodes correspondantes de la facette numpunct. Le nombre de chiffres significatifs après la virgule utilisé pour cette monnaie peut être obtenue grâce à la méthode frac_digits. Ce n'est que si la valeur renvoyée par cette méthode est supérieure à 0 que le symbole de séparation des unités de la partie fractionnaire de la méthode decimal_point est utilisé.

    La méthode curr_symbol permet d'obtenir le symbole monétaire de la monnaie. Ce symbole dépend de la valeur du paramètre template International. Si ce paramètre vaut true, le symbole monétaire renvoyé sera le symbole monétaire international. Dans le cas contraire, ce sera le symbole monétaire en usage dans le pays de circulation de la monnaie. La valeur du paramètre International pourra être obtenu grâce à la constante statique intl de la facette.

    Les méthodes suivantes permettent de spécifier le format d'écriture des montants positifs et négatifs. Ces méthodes utilisent les définitions de constantes et de types de la classe de base money_base dont la facette moneypunct hérite. La classe money_base est déclarée comme suit dans l'en-tête locale :

    class money_base
    {
    public:
    enum part
    {
    none, space, symbol, sign, value
    };
    struct pattern
    {
    char field[4];
    };
    };

    Cette classe contient la définition d'une énumération dont les valeurs permettent d'identifier les différentes composantes d'un montant, ainsi qu'une structure pattern qui contient un tableau de quatre caractères. Chacun de ces caractères peut prendre l'une des valeurs de l'énumération part. La structure pattern définit donc l'ordre dans lequel les composantes d'un montant doivent apparaître. Ce sont des motifs de ce genre qui sont renvoyés par les méthodes pos_format et neg_format, qui permettent d'obtenir respectivement le format des montants positifs et celui des montants négatifs.

    Les différentes valeurs que peuvent prendre les éléments du motif pattern représentent chacune une partie de l'expression d'un montant. La valeur value représente bien entendu la valeur de ce montant, sign son signe et symbol le symbole monétaire. La valeur space permet d'insérer un espace dans l'expression d'un montant, mais les espaces ne peuvent pas être utilisés en début et en fin de montants. Enfin, la valeur none permet de ne rien mettre à la position où il apparaît dans le motif.

    La manière d'écrire les montants positifs et négatifs varie grandement selon les pays. En général, il est courant d'utiliser le signe '-' pour signaler un montant négatif et aucun signe distinctif pour les montants positifs. Cependant, certains pays écrivent les montants négatifs entre parenthèses et la marque des montants négatifs n'est donc plus un simple caractère. Les méthodes positive_sign et negative_sign permettent d'obtenir les symboles à utiliser pour noter les montants positifs et négatifs. Elles retournent toutes les deux une chaîne de caractères, dont le premier est placé systématiquement à l'emplacement auquel la valeur sign a été affectée dans la chaîne de format renvoyée par les méthodes pos_format et neg_format. Les caractères résiduels, s'ils existent, sont placés à la fin de l'expression du montant complètement formatée. Ainsi, dans les locales pour lesquelles les montants négatifs sont écrits entre parenthèses, la chaîne renvoyée par la méthode negative_sign est « () », et pour les locales utilisant simplement le signe négatif, cette chaîne ne contient que le caractère '-'.

    16.2.5.2. Les facettes de lecture et d'écriture des montants

    Les facettes d'écriture et de lecture des montants sont sans doute les facettes standards les plus simples. En effet, elles ne disposent que de méthodes permettant d'écrire et de lire les montants sur les flux. Ces facettes sont respectivement les facettes money_put et money_get. Elles sont définies comme suit dans l'en-tête locale :

    template <class charT, bool Intl = false,
    class OutputIterator = ostreambuf_iterator<charT> >
    class money_put : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef OutputIterator iter_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit money_put(size_t refs = 0);

    // Les méthodes d'écriture des montants :
    iter_type put(iter_type s, bool intl, ios_base &f,
    char_type remplissage, long double units) const;
    iter_type put(iter_type s, bool intl, ios_base &f,
    char_type remplissage, const string_type &digits) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    template <class charT,
    class InputIterator = istreambuf_iterator<charT> >
    class money_get : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef InputIterator iter_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit money_get(size_t refs = 0);

    // Les méthodes de lecture des montants :
    iter_type get(iter_type s, iter_type end, bool intl,
    ios_base &f, ios_base::iostate &err,
    long double &units) const;
    iter_type get(iter_type s, iter_type end, bool intl,
    ios_base &f, ios_base::iostate &err,
    string_type &digits) const;
    static const bool intl = Intl;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Comme vous pouvez le constater, les méthodes d'écriture et de lecture put et get de ces facettes sont semblables aux méthodes correspondantes des facettes de gestion des nombres. Toutefois, elles se distinguent par un paramètre booléen complémentaire qui permet d'indiquer si les opérations de formatage doivent se faire en utilisant les conventions internationales d'écriture des montants. Les autres paramètres ont la même signification que pour les méthodes put et get des facettes de gestion des nombres. En particulier, l'itérateur fourni indique l'emplacement où les données doivent être écrites ou lues, et le flux d'entrée / sortie spécifié permet de récupérer les options de formatage des montants. L'une des options les plus utiles est sans doute l'option qui permet d'afficher la base des nombres, car, dans le cas des facettes de gestion des montants, elle permet d'activer ou non l'écriture du symbole monétaire. Enfin, les méthodes put et get sont fournies en deux exemplaires, un pour chaque type de donnée utilisable pour représenter les montants, à savoir les double et les chaînes de caractères.

    16.2.6. Les facettes de gestion du temps

    La bibliothèque standard ne fournit que deux facettes pour l'écriture et la lecture des dates : la facette time_put et la facette time_get. Ces deux facettes utilisent le type de base struct tm de la bibliothèque C pour représenter le temps. Bien que ce document ne décrive pas les fonctions de la bibliothèque C, il est peut-être bon de rappeler comment les programmes C manipulent les dates en général.

    La gestion du temps dans un programme peut très vite devenir un véritable cauchemar, principalement en raison de la complexité que les êtres humains se sont efforcés de développer dans leur manière de représenter le temps. En effet, il faut tenir compte non seulement des spécificités des calendriers (années bissextiles ou non par exemple), mais aussi des multiples bases de numération utilisées dans l'écriture des dates (24 heures par jour, 60 minutes par heure et 60 secondes par minutes, puis 10 dixièmes dans une seconde et ainsi de suite) et des conventions locales de gestion des heures (fuseau horaire, heure d'été et d'hiver). La règle d'or lors de la manipulation des dates est donc de toujours travailler dans un référentiel unique avec une représentation linéaire du temps, autrement dit, de simplifier tout cela. En pratique, cela revient à dire que les programmes doivent utiliser une représentation linéaire du temps (généralement, le nombre de secondes écoulées depuis une date de référence) et travailler en temps universel. De même, le stockage des dates doit être fait dans ce format afin de garantir la possibilité d'échanger les données sans pour autant laisser la place aux erreurs d'interprétation de ces dates.

    En pratique, la bibliothèque C utilise le type time_t. Les valeurs de ce type représentent le nombre d'instants écoulés depuis le premier janvier 1970 à 0 heure (date considérée comme le début de l'ère informatique par les inventeurs du langage C, Kernighan et Ritchie, et que l'on appelle couramment « Epoch »). La durée de ces instants n'est pas normalisée par la bibliothèque C, mais il s'agit de secondes pour les systèmes POSIX. Le type time_t permet donc de réaliser des calculs simplement sur les dates. Les dates représentées avec des time_t sont toujours exprimées en temps universel.

    Bien entendu, il existe des fonctions permettant de convertir les dates codées sous la forme de time_t en dates humaines et réciproquement. Le type de donnée utilisé pour stocker les dates au format humain est la structure struct tm. Cette structure contient plusieurs champs, qui représentent entre autres l'année, le jour, le mois, les heures, les minutes et les secondes. Ce type contient donc les dates au format éclaté et permet d'obtenir les différentes composantes d'une date.

    Généralement, les dates sont formatées en temps local, car les utilisateurs désirent souvent avoir les dates affichées dans leur propre base de temps. Cependant, il est également possible de formater les dates en temps universel. Ces opérations de formatages sont réalisées par les bibliothèques C et C++, et les programmes n'ont donc pas à se soucier des paramètres de fuseaux horaires, d'heure d'été et d'hiver et des conventions locales d'écriture des dates : tout est pris en charge par les locales.

    Les principales fonctions permettant de manipuler les dates sont récapitulées dans le tableau ci-dessous :

    Tableau 16-1. Fonctions C de gestion des dates

    FonctionDescription
    time_t time(time_t *)

    Permet d'obtenir la date courante. Peut être appelée avec l'adresse d'une variable de type time_t en paramètre ou avec la constante NULL. Initialise la variable passée par pointeur avec la date courante, et renvoie également la valeur écrite.

    struct tm *gmtime(const time_t *)

    Permet de convertir une date stockée dans une variable de type time_t en sa version éclatée en temps universel. Le pointeur renvoyé référence une structure allouée en zone statique par la bibliothèque C et ne doit pas être libéré.

    struct tm *localtime(const time_t *)

    Permet de convertir une date stockée dans une variable de type time_t en sa version éclatée en temps local. Le pointeur renvoyé référence une structure allouée en zone statique par la bibliothèque C et ne doit pas être libéré.

    time_t mktime(struct tm *)

    Permet de construire une date de type time_t à partir d'une date en temps local stockée dans une structure struct tm. Les données membres de la structure struct tm peuvent être corrigées par la fonction mktime si besoin est. Cette fonction est donc la fonction inverse de localtime.

    size_t strftime(char *tampon, size_t max, const char *format, constr struct tm *t)

    Permet de formater une date stockée dans une structure struct tm dans une chaîne de caractères. Cette chaîne doit être fournie en premier paramètre, ainsi que le nombre maximal de caractères que la fonction pourra écrire. La fonction renvoie le nombre de caractères écrits ou, si le premier paramètre est nul, la taille de la chaîne de caractères qu'il faudrait pour effectuer une écriture complète. La fonction strftime prend en paramètre une chaîne de format et fonctionne de manière similaire aux fonctions printf et sprintf. Elle comprend un grand nombre de formats, mais les plus utiles sont sans doute les formats « %X » et « %x », qui permettent respectivement de formater l'heure et la date selon les conventions de la locale du programme.

    Note : Il n'existe pas de fonction permettant de convertir une date exprimée en temps universel et stockée dans une structure struct tm en une date de type time_t. De même, la bibliothèque C ne fournit pas de fonction permettant d'analyser une chaîne de caractères représentant une date. Cependant, la norme Unix 98 définit la fonction strptime, qui est la fonction inverse de la fonction strftime.

    Les fonctions localtime et gmtime ne sont pas sûres dans un environnement multithreadé. En effet, la zone de mémoire renvoyée est en zone statique et est partagée par tous les threads. La bibliothèque C définit donc deux fonctions complémentaires, localtime_r et gmtime_r, qui prennent un paramètre complémentaire qui doit recevoir un pointeur sur la structure struct tm dans lequel le résultat doit être écrit. Cette structure est allouée par le thread appelant et ne risque donc pas d'être détruite par un appel à la même fonction par un autre thread.

    Les facettes de la bibliothèque standard C++ ne permettent pas de manipuler les dates en soi. Elles ne sont destinées qu'à réaliser le formatage des dates en tenant compte des spécificités de représentation des dates de la locale. Elles se comportent exactement comme la fonction strftime le fait lorsque l'on utilise les chaînes de format « %X » et « %x ».

    16.2.6.1. La facette d'écriture des dates

    La facette d'écriture des dates est déclarée comme suit dans l'en-tête locale :

    template <class charT,
    class OutputIterator = ostreambuf_iterator<charT> >
    class time_put : public locale::facet
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef OutputIterator iter_type;

    // Le constructeur :
    explicit time_put(size_t refs = 0);

    // Les méthodes d'écriture des dates :
    iter_type put(iter_type s, ios_base &f, char_type remplissage, const tm *t,
    char format, char modificateur = 0) const;
    iter_type put(iter_type s, ios_base &f, char_type remplissage, const tm *t,
    const charT *debut_format, const charT *fin_format) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Cette facette dispose de deux surcharges de la méthode put permettant d'écrire une date sur un flux de sortie. La première permet d'écrire une date sur le flux de sortie dont un itérateur est donné en premier paramètre. Le formatage de la date se fait comme avec la fonction strftime de la bibliothèque C. Le paramètre modificateur ne doit pas être utilisé en général, sa signification n'étant pas précisée par la norme C++. La deuxième forme de la méthode put réalise également une écriture sur le flux, en prenant comme chaîne de format la première sous-chaîne commençant par le caractère '%' dans la chaîne indiquée par les paramètres debut_format et fin_format.

    16.2.6.2. La facette de lecture des dates

    La facette de lecture des dates permet de lire les dates dans le même format que celui utilisé par la fonction strftime de la bibliothèque C lorsque la chaîne de format vaut « %X » ou « %x ». Cette facette est déclarée comme suit dans l'en-tête locale :

    template <class charT,
    class InputIterator = istreambuf_iterator<charT> >
    class time_get : public locale::facet, public time_base
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef InputIterator iter_type;

    // Le constructeur :
    explicit time_get(size_t refs = 0);

    // Les méthodes de gestion de la lecture des dates :
    iter_type get_time(iter_type s, iter_type end, ios_base &f,
    ios_base::iostate &err, tm *t) const;
    iter_type get_date(iter_type s, iter_type end, ios_base &f,
    ios_base::iostate &err, tm *t) const;
    iter_type get_weekday(iter_type s, iter_type end, ios_base &f,
    ios_base::iostate &err, tm *t) const;
    iter_type get_monthname(iter_type s, iter_type end, ios_base &f,
    ios_base::iostate &err, tm *t) const;
    iter_type get_year(iter_type s, iter_type end, ios_base &f,
    ios_base::iostate &err, tm *t) const;
    dateorder date_order() const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Les différentes méthodes de cette facette permettent respectivement d'obtenir l'heure, la date, le jour de la semaine, le nom du mois et l'année d'une date dans le flux d'entrée spécifié par l'itérateur fourni en premier paramètre. Toutes ces données sont interprétées en fonction de la locale à laquelle la facette appartient.

    Enfin, la méthode date_order permet d'obtenir l'une des valeurs de l'énumération définie dans la classe de base time_base et qui indique l'ordre dans lequel les composants jour / mois / année des dates apparaissent dans la locale de la facette. La classe de base time_base est déclarée comme suit dans l'en-tête locale :

    class time_base
    {
    public:
    enum dateorder
    {
    no_order, dmy, mdy, ymd, ydm
    };
    };

    La signification des différentes valeurs de l'énumération est immédiate. La seule valeur nécessitant des explications complémentaires est la valeur no_order. Cette valeur est renvoyée par la méthode date_order si le format de date utilisé par la locale de la facette contient d'autres champs que le jour, le mois et l'année.

    Note : La méthode date_order est fournie uniquement à titre de facilité par la bibliothèque standard. Elle peut ne pas être implémentée pour certaines locales. Dans ce cas, elle renvoie systématiquement la valeur no_order.

    16.2.7. Les facettes de gestion des messages

    Afin de faciliter l'internationalisation des programmes, la bibliothèque standard fournit la facette messages, qui permet de prendre en charge la traduction de tous les messages d'un programme de manière indépendante du système sous-jacent. Cette facette permet d'externaliser tous les messages des programmes dans des fichiers de messages que l'on appelle des catalogues. Le format et l'emplacement de ces fichiers ne sont pas spécifiés par la norme C++, cependant, la manière d'y accéder est standardisée et permet d'écrire des programmes portables. Ainsi, lorsqu'un programme devra être traduit, il suffira de traduire les messages stockés dans les fichiers de catalogue pour chaque langue et de les distribuer avec le programme.

    Note : La manière de créer et d'installer ces fichiers étant spécifique à chaque implémentation de la bibliothèque standard et, dans une large mesure, spécifique au système d'exploitation utilisé, ces fichiers ne seront pas décrits ici. Seule la manière d'utiliser la facette messages sera donc indiquée. Reportez-vous à la documentation de votre environnement de développement pour plus de détails sur les outils permettant de générer les fichiers de catalogue.

    La facette messages référence les fichiers de catalogue à l'aide d'un type de donnée spécifique. Ce type de donnée est défini dans la classe de base messages_base comme étant un type intégral :

    class messages_base
    {
    public:
    typedef int catalog;
    };
    La classe template messages de gestion de la facette hérite donc de cette classe de base et utilise le type catalog pour identifier les fichiers de catalogue de l'application.

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

    template <class charT>
    class messages : public locale::facet, public messages_base
    {
    public:
    // Les types de données :
    typedef charT char_type;
    typedef basic_string<charT> string_type;

    // Le constructeur :
    explicit messages(size_t refs = 0);

    // Les méthodes de gestion des catalogues de messages :
    catalog open(const basic_string<char> &nom, const locale &l) const;
    void close(catalog c) const;
    string_type get(catalog c, int groupe, int msg,
    const string_type &defaut) const;

    // L'identificateur de la facette :
    static locale::id id;
    };

    Note : Les méthodes virtuelles d'implémentation des méthodes publiques n'ont pas été écrites dans la déclaration précédente par souci de simplification. Elles existent malgré tout, et peuvent être redéfinies par les classes dérivées afin de personnaliser le comportement de la facette.

    Les principales méthodes de gestion des catalogues sont les méthodes open et close. Comme leurs noms l'indiquent, ces méthodes permettent d'ouvrir un nouveau fichier de catalogue et de le fermer pour en libérer les ressources. La méthode open prend en paramètre le nom du catalogue à ouvrir. Ce nom doit identifier de manière unique le catalogue, mais la norme C++ n'indique pas comment il doit être interprété. Cela relève donc de l'implémentation de la bibliothèque standard utilisée. Toutefois, en pratique, il est probable qu'il s'agit d'un nom de fichier. Le deuxième paramètre permet d'indiquer la locale à utiliser pour effectuer les conversions de jeux de caractères si cela est nécessaire. Il permet donc de laisser au programmeur le choix du jeu de caractères dans lequel les messages seront écrits dans le catalogue. La valeur renvoyée par la méthode open est l'identifiant du catalogue, identifiant qui devra être fourni à la méthode get pour récupérer les messages du catalogue et à la méthode close pour fermer le fichier de catalogue. Si l'ouverture du fichier n'a pas pu être effectuée, la méthode open renvoie une valeur inférieure à 0.

    Les messages du catalogue peuvent être récupérés à l'aide de la méthode get. Cette méthode prend en paramètre l'identifiant d'un catalogue précédemment obtenu par l'intermédiaire de la méthode open, un identifiant de groupe de message et un identifiant d'un message. Le dernier paramètre doit recevoir la valeur par défaut du message en cas d'échec de la recherche du message dans le catalogue. Cette valeur par défaut est souvent un message en anglais, ce qui permet au programme de fonctionner correctement même lorsque ses fichiers de catalogue sont vides.

    La manière dont les messages sont identifiés n'est pas spécifiée par la norme C++, tout comme la manière dont ils sont classés en groupes de messages au sein d'un même fichier de catalogue. Cela relève donc de l'implémentation de la bibliothèque utilisée. Consultez la documentation de votre environnement de développement pour plus de détails à ce sujet.

    Note : Cette facette est relativement peu utilisée, pour plusieurs raison. Premièrement, peu d'environnements C++ respectent la norme C++ à ce jour. Deuxièmement, les systèmes d'exploitation disposent souvent de mécanismes de localisation performants et pratiques. Enfin, l'identification d'un message par des valeurs numériques n'est pas toujours pratique et il est courant d'utiliser le message par défaut, souvent en anglais, comme clef de recherche pour les messages internationaux. Cette manière de procéder est en effet beaucoup plus simple, puisque le contenu des messages est écrit en clair dans la langue par défaut dans les fichiers sources du programme.


  • Commentaires

    Aucun commentaire pour le moment

    Suivre le flux RSS des commentaires


    Ajouter un commentaire

    Nom / Pseudo :

    E-mail (facultatif) :

    Site Web (facultatif) :

    Commentaire :