65 votes

Exceptions avec Unicode what()

Ou, "comment les Russes lancent-ils des exceptions ?".

La définition de std::exception est :

namespace std {
  class exception {
  public:
    exception() throw();
    exception(const exception&) throw();
    exception& operator=(const exception&) throw();
    virtual ~exception() throw();
    virtual const char* what() const throw();
  };
}

A école populaire de pensée pour concevoir des hiérarchies d'exceptions est de dériver de std::exception :

En général, il est préférable de lancer des objets, pas des meubles. Si possible, vous devriez lancer des instances de classes qui dérivent (en dernier ressort) de la classe std::exception . En faisant en sorte que votre classe d'exception hérite (en dernier ressort) de la classe de base standard de base des exceptions, vous vous vie plus facile pour vos utilisateurs (ils ont l'option d'attraper la plupart des choses via std::exception), et vous leur fournissez probablement leur fournir plus d'informations (comme le fait que votre exception particulière peut être un raffinement de std::runtime_error ou autre).

Mais face à Unicode, il semble impossible de concevoir une hiérarchie d'exceptions permettant d'atteindre les deux objectifs suivants :

  • Dérive ultimement de std::exception pour faciliter l'utilisation du site catch.
  • Assure la compatibilité Unicode pour que les diagnostics ne soient pas des tranches ou du charabia.

Il est assez simple de créer une classe d'exception qui peut être construite avec des chaînes Unicode. Mais la norme stipule que what() doit retourner un const char*, donc à un moment donné, les chaînes d'entrée doivent être converties en ASCII. Que cela soit fait au moment de la construction ou lorsque what() est appelé (si la chaîne source utilise des caractères non représentables par l'ASCII 7 bits), il peut être impossible de formater le message sans perte de fidélité.

Comment concevoir une hiérarchie d'exceptions qui combine l'intégration transparente d'une classe dérivée de std::exception avec des diagnostics Unicode sans perte ?

1 votes

Ce n'est pas grave, il suffit d'utiliser un codage qui utilise des octets. Selon moi, le plus gros problème avec std:.exception est que les classes dérivées en dérivent non virtuellement. De ce fait, il n'y a aucun moyen de dériver de votre propre classe de base, dérivée de std::exception et, dites, std::out_of_range .

0 votes

@sbi : C'est vrai, mais j'esquive ce problème en définissant ma hiérarchie uniquement en termes de std::exception directement. Je lance mon propre std::exception -et laisser les autres exceptions définies par le standard à la bibliothèque standard. Ce n'est pas une solution idéale, certes, mais pour mon usage, c'est la meilleure solution possible compte tenu de l'état actuel de la norme.

1 votes

Je viens de remarquer : Il semble que ce soit un doublon de : stackoverflow.com/questions/618111/

36voto

TheFogger Points 1156

Char* ne signifie pas ASCII. Vous pouvez utiliser un encodage Unicode 8 bits comme UTF-8. char peut aussi être 16 bits ou plus, vous pouvez alors utiliser UTF-16.

3 votes

Quelle est la prévalence des chaînes codées par le système qui utilisent des caractères en dehors du sous-ensemble ASCII ? Si les chaînes codées par le système peuvent être limitées au sous-ensemble ASCII, alors UTF-8 peut être utilisé sans texte bizarre. En ce qui concerne la longueur des chaînes, j'aime utiliser std::string parce que je peux en tirer un compte d'octets et calculer le nombre de caractères en O(n). En gros, si vous voulez que la chaîne de caractères pense en caractères, vous devez sous-classer std::basic_string<signed char> changer son itérateur (et peut-être le rétrograder d'itérateur à accès aléatoire), et ajouter une méthode de comptage des octets.

0 votes

@sbi : Je pense que vous m'avez mal compris. Ce que je voulais dire, c'est que la chaîne de texte renvoyée par la fonction what() pour les exceptions stdlib sont déjà des chaînes UTF-8 valides puisqu'elles sont ASCII et que l'ASCII est un sous-ensemble d'UTF-8. De plus, j'ai créé un gros "problème encombrant" à partir de vos deux problèmes puisque tous les problèmes avec UTF-8 commencent lorsque vous sortez du sous-ensemble ASCII. En parlant de solutions, j'aime bien la réponse acceptée, postée dans le fil de discussion posté par ybungalobill ci-dessous.

10voto

Martin Ba Points 10243

Renvoyer UTF-8 est un choix évident. Si l'application qui utilise vos exceptions utilise un codage multi-octets différent, elle pourrait avoir du mal à afficher la chaîne de caractères. (Elle ne peut pas savoir que c'est UTF-8, n'est-ce pas ?) D'un autre côté, pour les encodages 8 bits ISO-8859-* (Europe de l'Ouest, cyrillique, etc.), l'affichage d'une chaîne UTF-8 ne fera qu'afficher du charabia et cela peut vous convenir (ou à votre utilisateur) si vous ne pouvez pas faire la distinction entre un char* dans le jeu de caractères local et UTF-8.

Personnellement, je pense que seuls les messages d'erreur de bas niveau devraient aller dans les chaînes what() et personnellement, je pense que ceux-ci devraient être en anglais de toute façon. (Peut-être combiné avec un numéro d'erreur ou autre).

Le pire problème que je vois avec what() est qu'il n'est pas rare d'inclure certains détails contextuels dans le message what(), par exemple une nom de fichier . Les noms de fichiers sont non ASCII assez souvent, vous n'avez donc pas d'autre choix que d'utiliser UTF-8 en tant que what() l'encodage.

Notez également que votre classe d'exception (qui est dérivée de std::exception) peut évidemment fournir toutes les méthodes d'accès que vous souhaitez et qu'il pourrait donc être judicieux d'ajouter une fonction explicite what_utf8() o what_utf16() o what_iso8859_5() .

Edit : Concernant le commentaire de John sur la façon de retourner UTF-8 :

Si vous avez un const char* what() cette fonction renvoie essentiellement un tas d'octets. Sur une plateforme Windows d'Europe de l'Ouest, ces octets sont généralement codés comme suit Win1252 mais sur un Windows russe, cela pourrait aussi bien être Win1251 .

Ce que les octets retournés signifient dépend de leur encodage et leur encodage dépend de leur "origine" (et de la personne qui les interprète). L'encodage d'une chaîne littérale est défini au moment de la compilation, mais au moment de l'exécution, c'est à l'application de décider comment l'interpréter.

Donc, pour que votre exception renvoie des chaînes UTF-8 avec what() (ou what_utf8() ) vous devez vous assurer que :

  • Le message d'entrée de votre exception a un codage bien défini
  • Vous disposez d'un encodage bien défini pour le membre de la chaîne de caractères que vous utilisez pour contenir le message.
  • Vous convertissez le codage de manière appropriée lorsque what() s'appelle

Exemple :

struct MyExc : virtual public std::exception {
  MyExc(const char* msg)
  : exception(msg)
  { }
  std::string what_utf8() {
    return convert_iso8859_1_to_utf8( what() );
  }
};

// In a ISO-8859-1 encoded source file
const char* my_err_msg = "ISO-8859-1 ... äöüß ...";
...
throw MyExc(my_err_msg);
...
catch(MyExc const& e) {
  std::string iso8859_1_msg = e.what();
  std::string utf_msg = e.what_utf8();
...

La conversion pourrait également être placée dans la fonction membre (surchargée) what() de MyExc(). ou vous pourriez définir l'exception pour qu'elle prenne une chaîne de caractères déjà codée en UTF-8 ou vous pourriez convertir (à partir d'un encodage d'entrée attendu, peut-être wchar_t/UTF-16) dans le ctor.

0 votes

"Renvoyer UTF-8 est un choix évident." Cela semble suivre l'arc de la pensée actuelle. Maintenant, la seule question est, comment puis-je retourner UTF-8 ? :)

0 votes

@John Dibling:Si le texte de vos messages est entièrement en anglais et peut être exprimé en ASCII standard, vous en avez déjà fait assez car l'ASCII et les 128 premiers caractères de l'UTF-8 sont identiques. Si vous utilisez des caractères et un encodage supérieur à 127, vous devrez convertir l'encodage en UTF-8. Il doit y avoir une fonction standard de la bibliothèque C++ pour le faire maintenant. Sinon, libiconv peut faire l'affaire.

2 votes

@JeremyP : nous utilisons ICU où je travaille pour gérer l'Unicode, certainement pas parfait (interface C...) mais il fait le travail et gère les bizarreries de l'Unicode / Internationalisation / Localisation.

4voto

Loki Astari Points 116129

La première question est de savoir ce que vous avez l'intention de faire avec la chaîne what().

Prévoyez-vous d'enregistrer ces informations quelque part ?

Si c'est le cas, vous ne devriez pas utiliser le contenu de la chaîne what(), mais plutôt utiliser cette chaîne comme référence pour rechercher la bonne réponse. local un message de journalisation spécifique. Selon moi, le contenu de la fonction what() n'est pas destiné à la journalisation (ou à toute forme d'affichage), mais à la recherche de la chaîne de journalisation (qui peut être n'importe quelle chaîne Unicode).

Maintenant, il peut être utile que la chaîne what() contienne un message lisible par l'homme pour les développeurs afin d'aider à un débogage rapide (mais pour cela, un texte poli très lisible n'est pas nécessaire). Par conséquent, il n'y a aucune raison de prendre en charge autre chose que l'ASCII. Respectez le principe KISS.

0 votes

En réponse à vos questions. J'aimerais utiliser le what() afin de générer deux niveaux de diagnostic. Le niveau inférieur est un diagnostic centré sur le développeur ou le technicien qui serait affiché dans les fichiers journaux. Mais à un niveau plus élevé, j'aimerais que ces chaînes soient utilisées pour construire un diagnostic qui soit exploitable par un être humain normal. Comme vous semblez l'impliquer, le what() Le retour pourrait simplement être une valeur de consultation dans une table de messages plus humains, mais certains composants de la chaîne (ou au moins l'exception) devraient être lisibles par l'homme, comme " Le fichier blah.txt est introuvable ".

0 votes

Un autre de mes objectifs est de garder catch à un minimum. L'utopie serait d'avoir un seul catch( const std::exception& ex ) qui attrape tout, et ce bloc consommera le fichier what() pour produire des diagnostics de niveau technicien et humain. En suivant ce modèle, toutes les données nécessaires à la construction des deux messages doivent pouvoir être récupérées dans le fichier what() chaîne.

0 votes

La plupart des langages de conversion locaux prennent une chaîne en entrée et la convertissent en chaîne locale via des ressources. Ainsi, si vous dites que la première partie de la chaîne de caractères jusqu'aux deux points est utilisée pour rechercher les chaînes de caractères locales, vous pouvez alors faire ceci : File could not be found: blah.txt . La partie File could not be found: peut alors être utilisé pour rechercher la traduction spécifique locale.

3voto

Steve M Points 4137

Un const char* n'a pas besoin de pointer vers une chaîne ASCII ; il peut être dans un encodage multi-octet tel que UTF-8. Une option consiste à utiliser wcstombs() et ses amis pour convertir les wstrings en strings, mais vous pouvez avoir à convertir le résultat de what() en wstring avant l'impression. Cela implique également plus de copie et d'allocation de mémoire que ce que vous pouvez supporter dans un gestionnaire d'exception.

En général, je définis simplement ma propre classe d'exception de base, qui utilise wstring au lieu de string dans le constructeur et renvoie un const wstring& à partir de what() . Ce n'est pas si grave. L'absence d'un standard est un gros oubli.

Une autre opinion valable est que les chaînes d'exception ne doivent jamais être présentées à l'utilisateur, de sorte que leur localisation n'est pas nécessaire et que vous n'avez pas à vous soucier de ce qui précède.

0 votes

+1 Créer votre propre classe d'exception est la chose la plus raisonnable à faire IMHO. Si vous attrapez une exception std::exception, il n'y a pas grand chose à faire si l'encodage est inconnu (est-ce CP1252 ou UTF-8 ?) Si vous avez votre propre classe d'exception, le problème est résolu.

0voto

Dustin Getz Points 8514

Le minimum absolu que tout développeur de logiciels doit absolument, positivement connaître sur Unicode et les jeux de caractères (pas d'excuses !) par Joel Spolsky

Edit : Made CW, les commentateurs peuvent ajouter la raison pour laquelle ce lien est pertinent s'ils le souhaitent.

6 votes

-1 : Je pense que l'ajout d'un lien (un excellent lien, d'ailleurs) sans aucune explication sur la façon dont cela se rapporte aux exceptions C++ ne fait pas l'affaire. rien pour aider respuesta la question. (Cela pourrait aider à contextualiser certains problèmes d'encodage, mais c'est à cela que servent les commentaires, non ?) Ceci est particulièrement vrai si le PO a besoin de lire le lien.

4 votes

De plus, j'ai déjà lu le lien et il ne répond pas à ma question.

2 votes

Au contraire, je pense que ce lien fournit grand de comprendre pourquoi l'utilisation char const* n'a rien à voir avec le codage des caractères.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X