16.3. Personnalisation des mécanismes de localisation

Les mécanismes de localisation ont été conçus de telle sorte que le programmeur peut, s'il le désire (et s'il en a réellement le besoin), personnaliser leur fonctionnement. Ainsi, il est parfaitement possible de définir de nouvelles facettes, par exemple pour permettre la localisation des types de données complémentaires définis par le programme. De même, il est possible de redéfinir les méthodes virtuelles des classes de gestion des facettes standards de la bibliothèque et de remplacer les facettes originales par des facettes personnalisées. Cependant, il faut bien reconnaître que la manière de procéder n'est pas très pratique, et en fait les mécanismes internes de gestion des facettes semblent être réservés aux classes et aux méthodes de la bibliothèque standard elle-même.

16.3.1. Création et intégration d'une nouvelle facette

Comme il l'a été expliqué dans la Section 16.1, une facette n'est rien d'autre qu'une classe dérivant de la classe locale::facet et contenant une donnée membre statique id. Cette donnée membre est utilisée par les classes de locale pour identifier le type de la facette et pour l'intégrer dans le mécanisme de gestion des facettes standards.

L'exemple suivant montre comment on peut réaliser deux facettes permettant d'encapsuler les spécificités d'un type de donnée défini par le programme, le type answer_t. Ce type est supposé permettre la création de variables contenant la réponse de l'utilisateur à une question. Ce n'est rien d'autre qu'une énumération contenant les valeurs no (pour la réponse négative), yes (pour l'affirmative), all (pour répondre par l'affirmative pour tout un ensemble d'éléments) et none (pour répondre par la négative pour tout un ensemble d'éléments).

Dans cet exemple, deux facettes sont définies : la facette answerpunct, qui prend en charge la localisation des noms des différentes valeurs de l'énumération answer_t, et la facette answer_put, qui prend en charge le formatage des valeurs de cette énumération dans un flux standard. L'opérateur operator<< est également défini, afin de présenter la manière dont ces facettes peuvent être utilisées. La facette answer_get et l'opérateur correspondant operator>> n'ont pas été définis et sont laissés en exercice pour le lecteur intéressé.

Exemple 16-6. Définition de nouvelles facettes

#include <iostream>
#include <locale>

using namespace std;

// Nouveau type de donnée permettant de gérer les réponses
// aux questions (yes / no / all / none) :
enum answer_t
{
    no, yes, all, none
};

// Facette prenant définissant les noms des réponses :
template <class charT>
class answerpunct : public locale::facet
{
public:
    // Les types de données :
    typedef charT char_type;
    typedef basic_string<charT> string_type;

    // L'identifiant de la facette :
    static locale::id id;

    // Le constructeur :
    answerpunct(size_t refs = 0) : locale::facet(refs)
    {
    }

    // Les méthodes permettant d'obtenir les noms des valeurs :
    string_type yesname() const
    {
        return do_yesname();
    }

    string_type noname() const
    {
        return do_noname();
    }

    string_type allname() const
    {
        return do_allname();
    }

    string_type nonename() const
    {
        return do_nonename();
    }

protected:
    // Le destructeur :
    virtual ~answerpunct()
    {
    }

    // Les méthodes virtuelles :
    virtual string_type do_yesname() const
    {
        return "yes";
    }

    virtual string_type do_noname() const
    {
        return "no";
    }

    virtual string_type do_allname() const
    {
        return "all";
    }

    virtual string_type do_nonename() const
    {
        return "none";
    }
};

// Instanciation de l'identifiant de la facette answerpunct :
template <class charT>
locale::id answerpunct<charT>::id;

// Facette prenant en charge le formatage des réponses :
template <class charT,
    class OutputIterator = ostreambuf_iterator<charT> >
class answer_put : public locale::facet
public:
    // Les types de données :
    typedef charT char_type;
    typedef OutputIterator      iter_type;
    typedef basic_string<charT> string_type;

    // L'identifiant de la facette :
    static locale::id id;

    // Le constructeur :
    answer_put(size_t refs = 0) : locale::facet(refs)
    {
    }

    // La méthode de formatage publique :
    iter_type put(iter_type i, ios_base &flux,
        char_type remplissage, answer_t valeur) const
    {
        return do_put(i, flux, remplissage, valeur);
    }

protected:
    // Le destructeur :
    virtual ~answer_put()
    {
    }

    // L'implémentation de la méthode de formatage :
    virtual iter_type do_put(iter_type i, ios_base &flux,
        char_type remplissage, answer_t valeur) const
    {
        // Récupère la facette décrivant les noms de types :
        const answerpunct<charT> &facet =
            use_facet<answerpunct<charT> >(flux.getloc());
        // Récupération du nom qui sera écrit :
        string_type result;
        switch (valeur)
        {
        case yes:
            result = facet.yesname();
            break;
        case no:
            result = facet.noname();
            break;
        case all:
            result = facet.allname();
            break;
        case none:
            result = facet.nonename();
            break;
        }
        // Écriture de la valeur :
        const char *p = result.c_str();
        while (*p != 0)
        {
            *i = *p;
            ++i; ++p;
        }
        return i;
    }
};

// Instanciation de l'identifiant de la facette answer_put :
template <class charT,
    class OutputIterator = ostreambuf_iterator<charT> >
locale::id answer_put<charT, OutputIterator>::id;

// Opérateur permettant de formater une valeur
// de type answer_t dans un flux de sortie :
template <class charT, class Traits>
basic_ostream<charT, Traits> &operator<<(
    basic_ostream<charT, Traits> &flux,
    answer_t valeur)
{
    // Initialisation du flux de sortie :
    typename basic_ostream<charT, Traits>::sentry init(flux);
    if (init)
    {
        // Récupération de la facette de gestion de ce type :
        const answer_put<charT> &facet =
            use_facet<answer_put<charT> >(flux.getloc());
        // Écriture des données :
        facet.put(flux, flux, ' ', valeur);
    }
    return flux;
}

int main(void)
{
    // Crée une nouvelle locale utilisant nos deux facettes :
    locale temp(locale(""), new answerpunct<char>);
    locale loc(temp, new answer_put<char>);
    // Installe cette locale dans le flux de sortie :
    cout.imbue(loc);
    // Affiche quelques valeurs de type answer_t :
    cout << yes << endl;
    cout << no << endl;
    cout << all << endl;
    cout << none << endl;
    return 0;
}

Note : Cet exemple, bien que déjà compliqué, passe sous silence un certain nombre de points qu'il faudrait théoriquement prendre en compte pour réaliser une implémentation correcte des facettes et des opérateurs d'insertion et d'extraction des données de type answer_t dans les flux standards. Il faudrait en effet traiter les cas d'erreurs lors des écritures sur le flux de sortie dans la méthode do_put de la facette answer_put, capter les exceptions qui peuvent se produire, corriger l'état du flux d'entrée / sortie au sein de l'opérateur operator<< et relancer ces exceptions.

De même, les paramètres de la locale ne sont absolument pas pris en compte dans la facette answerpunct, alors qu'une implémentation complète devrait s'en soucier. Pour cela, il faudrait récupérer le nom de la locale incluse dans les flux d'entrée / sortie d'une part, et définir une facette spécialisée answerpunct_byname, en fonction du nom de laquelle les méthodes do_yesname, do_noname, do_allname et do_nonename devraient s'adapter. La section suivante donne un exemple de redéfinition d'une facette existante.

16.3.2. Remplacement d'une facette existante

La redéfinition des méthodes de facettes déjà existantes est légèrement plus simple que l'écriture d'une nouvelle facette. En effet, il n'est plus nécessaire de définir la donnée membre statique id. De plus, seules les méthodes qui doivent réellement être redéfinies doivent être récrites.

L'exemple suivant présente comment un programme peut redéfinir les méthodes do_truename et do_falsename de la facette standard numpunct_byname afin d'en fournir une version localisée en français. Cela permet d'utiliser ces noms français dans les opérations de formatage des flux d'entrée / sortie standards, lorsque le manipulateur boolalpha a été utilisé.

Exemple 16-7. Spécialisation d'une facette existante

#include <iostream>
#include <locale>
#include <clocale>
#include <cstring>

using namespace std;

// Facette destinée à remplacer numpunct_byname :
class MyNumpunct_byname :
    public numpunct_byname<char>
{
    // Les noms des valeurs true et false :
    const char *m_truename;
    const char *m_falsename;

public:
    MyNumpunct_byname(const char* nom) :
        numpunct_byname<char>(nom)
    {
        // Détermine le nom de la locale active :
        const char *loc = nom;
        if (strcmp(nom, "") == 0)
         {
            // Récupère le nom de la locale globale active :
            loc = setlocale(0, NULL);
        }
        // Prend en charge les noms français :
        if (strcmp(loc, "fr_FR") == 0)
        {
            m_truename = "vrai";
            m_falsename = "faux";
        }
        else
        {
            // Pour les autres locales, utilise les noms anglais :
            m_truename = "true";
            m_falsename = "false";
        }
    }

protected:
    ~MyNumpunct_byname()
    {
    }

    string do_truename() const
    {
        return m_truename;
    }

    string do_falsename() const
    {
        return m_falsename;
    }
};

int main(void)
{
    // Fixe la locale globale du programme :
    locale::global(locale(""));
    // Crée une nouvelle locale utilisant notre facette :
       locale l(locale(""), new MyNumpunct_byname(""));
    // Installe cette locale dans le flux de sortie :
    cout.imbue(l);
    // Affiche deux booléens :
    cout << boolalpha << true << endl;
    cout << false << endl;
    return 0;
}

Note : La classe de base de la facette MyNumpunct_byname est la classe numpunct_byname parce que la facette a besoin de connaître le nom de la locale pour laquelle elle est construite. En effet, aucun autre mécanisme standard ne permet à une facette de récupérer ce nom et donc de s'adapter aux différentes locales existantes. Vous remarquerez que les facettes de formatage n'ont pas besoin de connaître ce nom puisqu'elles peuvent le récupérer grâce à la méthode name de la locale du flux sur lequel elles travaillent.

La facette MyNumpunct_byname utilise la fonction setlocale de la bibliothèque C pour récupérer le nom de la locale courante si elle est initialisée avec un nom vide. En réalité, elle devrait récupérer ce nom par ses propres moyens et effectuer les traductions des noms des valeurs true et false par elle-même, car cela suppose que la locale globale du programme est initialisée avec le même nom. C'est pour cela que le programme principal commence par appeler la méthode global de la classe local avec comme paramètre une locale anonmyme. Cela dit, les mécanismes permettant à un programme de récupérer les paramètres de la locale définie dans l'environnement d'exécution du programme sont spécifiques à chaque système et ne peuvent donc pas être décrits ici.

Bien entendu, si d'autres langues que le français devaient être prises en compte, d'autre mécanismes plus génériques devraient également être mis en place pour définir les noms des valeurs true et false afin d'éviter de compliquer exagérément le code de la facette.