Quel est le «problème» avec le C ++ wchar_t et wstrings? Quelles sont les alternatives aux caractères larges?

86

J'ai vu beaucoup de gens dans la communauté C ++ (en particulier ## c ++ sur freenode) se plaindre de l'utilisation de wstringsand wchar_t, et de leur utilisation dans l'API Windows. Qu'est-ce qui ne va pas exactement avec wchar_tet wstring, et si je veux soutenir l'internationalisation, quelles sont les alternatives aux caractères larges?

Ken Li
la source
1
Avez-vous des références pour cela?
Dani
14
Peut-être que ce fil génial répondra à toutes vos questions? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox
15
Sous Windows, vous n'avez pas vraiment le choix. Ses API internes ont été conçues pour UCS-2, ce qui était raisonnable à l'époque car c'était avant que les codages UTF-8 et UTF-16 de longueur variable ne soient normalisés. Mais maintenant qu'ils prennent en charge UTF-16, ils se sont retrouvés avec le pire des deux mondes.
jamesdlin
12
utf8everywhere.org a une bonne discussion sur les raisons d'éviter les caractères larges.
JoeG
5
@jamesdlin Vous avez certainement le choix. nowide Library fournit un moyen pratique de convertir des chaînes juste lors de leur transmission aux API. Les appels d'API avec des chaînes sont généralement de basse fréquence, donc le moyen raisonnable est de convertir ad-hok et d'avoir des fichiers et des variables internes en UTF-8 tout le temps.
Pavel Radzivilovsky

Réponses:

114

Qu'est-ce que wchar_t?

wchar_t est défini de telle sorte que le codage char de tout paramètre régional peut être converti en une représentation wchar_t où chaque wchar_t représente exactement un point de code:

Le type wchar_t est un type distinct dont les valeurs peuvent représenter des codes distincts pour tous les membres du plus grand jeu de caractères étendu spécifié parmi les paramètres régionaux pris en charge (22.3.1).

                                                                               - C ++ [basic.fundamental] 3.9.1 / 5

Cela ne nécessite pas que wchar_t soit suffisamment grand pour représenter un caractère de tous les paramètres régionaux simultanément. Autrement dit, le codage utilisé pour wchar_t peut différer entre les paramètres régionaux. Ce qui signifie que vous ne pouvez pas nécessairement convertir une chaîne en wchar_t en utilisant une locale, puis la reconvertir en char en utilisant une autre locale. 1

Puisque l'utilisation de wchar_t comme représentation commune entre tous les paramètres régionaux semble être la principale utilisation de wchar_t dans la pratique, vous vous demandez peut-être à quoi cela sert sinon cela.

L'intention et le but d'origine de wchar_t était de simplifier le traitement de texte en le définissant de telle sorte qu'il nécessite un mappage un-à-un des unités de code d'une chaîne aux caractères du texte, permettant ainsi l'utilisation des mêmes algorithmes simples que ceux utilisés avec des chaînes ascii pour travailler avec d'autres langues.

Malheureusement, le libellé de la spécification de wchar_t suppose un mappage un à un entre les caractères et les points de code pour y parvenir. Unicode rompt cette hypothèse 2 , vous ne pouvez donc pas non plus utiliser wchar_t en toute sécurité pour des algorithmes de texte simples.

Cela signifie que les logiciels portables ne peuvent pas utiliser wchar_t ni comme représentation commune du texte entre les paramètres régionaux, ni pour permettre l'utilisation d'algorithmes de texte simples.

À quoi sert wchar_t aujourd'hui?

Pas grand chose, pour le code portable de toute façon. Si __STDC_ISO_10646__est défini, alors les valeurs de wchar_t représentent directement des points de code Unicode avec les mêmes valeurs dans tous les paramètres régionaux. Cela permet d'effectuer en toute sécurité les conversions inter-locales mentionnées précédemment. Cependant, vous ne pouvez pas vous fier uniquement à lui pour décider que vous pouvez utiliser wchar_t de cette façon car, alors que la plupart des plates-formes unix le définissent, Windows ne le fait pas même si Windows utilise les mêmes paramètres régionaux wchar_t dans tous les paramètres régionaux.

La raison pour laquelle Windows ne définit pas __STDC_ISO_10646__est parce que Windows utilise UTF-16 comme son encodage wchar_t, et parce que UTF-16 utilise des paires de substitution pour représenter des points de code supérieurs à U + FFFF, ce qui signifie que UTF-16 ne répond pas aux exigences de __STDC_ISO_10646__.

Pour le code spécifique à la plate-forme, wchar_t peut être plus utile. C'est essentiellement nécessaire sur Windows (par exemple, certains fichiers ne peuvent tout simplement pas être ouverts sans utiliser les noms de fichiers wchar_t), bien que Windows soit la seule plate-forme où cela est vrai pour autant que je sache (donc peut-être que nous pouvons penser à wchar_t comme `` Windows_char_t '').

Avec le recul, wchar_t n'est clairement pas utile pour simplifier la gestion de texte, ou comme stockage pour du texte indépendant des paramètres régionaux. Le code portable ne doit pas tenter de l'utiliser à ces fins. Un code non portable peut le trouver utile simplement parce que certaines API l'exigent.

Alternatives

L'alternative que j'aime est d'utiliser des chaînes C encodées en UTF-8, même sur des plates-formes pas particulièrement adaptées à UTF-8.

De cette façon, on peut écrire du code portable en utilisant une représentation textuelle commune sur toutes les plates-formes, utiliser des types de données standard pour leur usage prévu, obtenir le support du langage pour ces types (par exemple, des chaînes littérales, bien que certaines astuces soient nécessaires pour le faire fonctionner pour certains compilateurs), certains support de bibliothèque standard, support de débogueur (plus de trucs peuvent être nécessaires), etc. Avec des caractères larges, il est généralement plus difficile voire impossible d'obtenir tout cela, et vous pouvez obtenir différentes pièces sur différentes plates-formes.

Une chose que UTF-8 ne fournit pas est la possibilité d'utiliser des algorithmes de texte simples tels que ceux possibles avec ASCII. Dans cet UTF-8 n'est pas pire que tout autre encodage Unicode. En fait, cela peut être considéré comme meilleur car les représentations d'unité multi-code en UTF-8 sont plus courantes et donc les bogues dans la gestion du code de telles représentations de caractères à largeur variable sont plus susceptibles d'être remarqués et corrigés que si vous essayez de vous en tenir à UTF -32 avec NFC ou NFKC.

De nombreuses plates-formes utilisent UTF-8 comme encodage de caractères natif et de nombreux programmes ne nécessitent pas de traitement de texte significatif, et donc l'écriture d'un programme internationalisé sur ces plates-formes est peu différente de l'écriture de code sans considérer l'internationalisation. L'écriture de code plus largement portable ou l'écriture sur d'autres plates-formes nécessite l'insertion de conversions aux limites des API qui utilisent d'autres encodages.

Une autre alternative utilisée par certains logiciels est de choisir une représentation multiplateforme, telle que des tableaux courts non signés contenant des données UTF-16, puis de fournir tout le support de la bibliothèque et de vivre simplement avec les coûts de prise en charge de la langue, etc.

C ++ 11 ajoute de nouveaux types de caractères larges comme alternatives à wchar_t, char16_t et char32_t avec des fonctionnalités de langage / bibliothèque. Il n'est pas garanti que ce soit UTF-16 et UTF-32, mais je n'imagine pas qu'une implémentation majeure utilisera autre chose. C ++ 11 améliore également la prise en charge de UTF-8, par exemple avec les littéraux de chaîne UTF-8, il ne sera donc pas nécessaire de tromper VC ++ en produisant des chaînes encodées en UTF-8 (bien que je puisse continuer à le faire plutôt que d'utiliser le u8préfixe) .

Alternatives à éviter

TCHAR: TCHAR est destiné à la migration d'anciens programmes Windows qui supposent des encodages hérités de char vers wchar_t, et il vaut mieux l'oublier à moins que votre programme n'ait été écrit dans un millénaire précédent. Il n'est pas portable et est intrinsèquement non spécifique quant à son encodage et même à son type de données, ce qui le rend inutilisable avec une API non basée sur TCHAR. Puisque son but est la migration vers wchar_t, ce que nous avons vu ci-dessus n'est pas une bonne idée, il n'y a aucune valeur à utiliser TCHAR.


1. Les caractères qui sont représentables dans les chaînes wchar_t mais qui ne sont pris en charge dans aucune locale ne sont pas obligés d'être représentés avec une seule valeur wchar_t. Cela signifie que wchar_t pourrait utiliser un codage à largeur variable pour certains caractères, une autre violation claire de l'intention de wchar_t. Bien qu'il soit discutable qu'un caractère représentable par wchar_t soit suffisant pour dire que la locale `` prend en charge '' ce caractère, auquel cas les encodages à largeur variable ne sont pas légaux et l'utilisation par Window de UTF-16 n'est pas conforme.

2. Unicode permet de représenter de nombreux caractères avec plusieurs points de code, ce qui crée les mêmes problèmes pour les algorithmes de texte simples que pour les encodages à largeur variable. Même si l'on maintient strictement une normalisation composée, certains caractères nécessitent encore plusieurs points de code. Voir: http://www.unicode.org/standard/where/

bames53
la source
3
Ajout: utf8everywhere.org recommande d'utiliser UTF-8 sous Windows, et Boost.Nowide est prévu pour une révision formelle.
Yakov Galka
2
La meilleure chose, bien sûr, est d'utiliser C # ou VB.Net sur Windows :) Ou tout simplement l'ancien C / Win32. Mais si vous devez utiliser C ++, alors TCHAR est la meilleure solution. Qui est par défaut "wchar_t" sur MSVS2005 et supérieur. IMHO ...
paulsm4
4
@BrendanMcK: Bien sûr, le code qui utilise l'API Win32 sur Windows et d'autres API sur d'autres systèmes n'existe pas. Droite? Le problème avec l'approche de Microsoft ("utiliser wchar en interne partout dans votre application") est qu'elle affecte même le code qui n'interface pas directement avec le système et pourrait être portable.
Yakov Galka
4
Le problème est que vous devez utiliser des fonctions spécifiques à Windows, car la décision de Microsoft de ne pas prendre en charge UTF-8 en tant que page de codes ANSI «casse» la bibliothèque C (++) standard. Par exemple, vous ne pouvez pas fopenun fichier dont le nom contient des caractères non ANSI.
dan04
11
@ dan04 Oui, vous ne pouvez pas utiliser la bibliothèque standard sous Windows, mais vous pouvez créer une interface portable qui encapsule la bibliothèque standard sur d'autres plates-formes et convertit d'UTF-8 en wchar_t directement avant d'utiliser les fonctions Win32 W.
bames53
20

Il n'y a rien de "mal" avec wchar_t. Le problème est que, à l'époque de NT 3.x, Microsoft a décidé qu'Unicode était bon (c'est le cas) et d'implémenter Unicode sous forme de caractères 16 bits wchar_t. Ainsi, la plupart des publications Microsoft du milieu des années 90 assimilaient à peu près Unicode == utf16 == wchar_t.

Ce qui, malheureusement, n’est pas du tout le cas. Les «caractères larges» ne sont pas forcément 2 octets, sur toutes les plateformes, dans toutes les circonstances.

C'est l'une des meilleures amorces sur "Unicode" (indépendante de cette question, indépendante du C ++) que j'ai jamais vue: je le recommande vivement :

Et je crois honnêtement que la meilleure façon de traiter "ASCII 8 bits" vs "caractères Win32 larges" vs "wchar_t-en-général" est simplement d'accepter que "Windows est différent" ... et coder en conséquence.

A MON HUMBLE AVIS...

PS:

Je suis totalement d'accord avec jamesdlin ci-dessus:

Sous Windows, vous n'avez pas vraiment le choix. Ses API internes ont été conçues pour UCS-2, ce qui était raisonnable à l'époque car c'était avant la normalisation des codages UTF-8 et UTF-16 de longueur variable. Mais maintenant qu'ils prennent en charge UTF-16, ils se sont retrouvés avec le pire des deux mondes.

paulsm4
la source