web-dev-qa-db-fra.com

Comment utiliser correctement std :: string sur UTF-8 en C ++?

Ma plate-forme est un Mac et C++ 11 (ou supérieur). Je suis un débutant en C++ et travaille sur un projet personnel qui traite le chinois et l'anglais. UTF-8 est l'encodage préféré pour ce projet.

J'ai lu quelques articles sur Stack Overflow, et beaucoup suggèrent d'utiliser std::string pour traiter UTF-8 et d'éviter wchar_t car il n'y a pas de char8_t pour le moment pour UTF-8.

Cependant, aucun d'entre eux ne dit comment gérer correctement des fonctions telles que str[i], std::string::size(), std::string::find_first_of() ou std::regex, car ces fonctions renvoient généralement des résultats inattendus face à UTF-8.

Devrais-je continuer avec std::string ou passer à std::wstring? Si je devais rester avec std::string, quelle est la meilleure pratique pour traiter les problèmes ci-dessus?

55
stackunderflow

Glossaire Unicode

Unicode est un sujet vaste et complexe. Je ne souhaite pas trop y aller, cependant un glossaire rapide est nécessaire:

  1. Points de code: Les points de code sont les blocs de construction de base d'Unicode, un point de code est simplement un entier mappé sur un signifiant . La partie entière correspond à 32 bits (enfin, 24 bits vraiment), et le sens peut être une lettre, un diacritique, un espace blanc, un signe, un smiley, un demi drapeau, ... et cela peut même être "le la partie suivante se lit de droite à gauche ".
  2. grappes de graphèmes: les grappes de graphèmes sont des groupes de points de code liés sémantiquement, par exemple, un drapeau en unicode est représenté en associant deux points de code; chacun de ces deux, pris isolément, n'a aucune signification, mais associés dans un cluster de graphèmes, ils représentent un drapeau. Les grappes de graphèmes sont également utilisés pour associer une lettre à un diacritique dans certains scripts.

C'est la base de l'Unicode. La distinction entre Point de code et grappe de graphèmes peut être généralement occultée car, dans la plupart des langues modernes, chaque "caractère" est mappé à un seul point de code (il existe des formes accentuées dédiées pour les combinaisons lettre + diacritiques couramment utilisées). Néanmoins, si vous vous aventurez dans des smileys, des drapeaux, etc., vous devrez peut-être faire attention à la distinction.


Primer UTF

Ensuite, une série de points de code Unicode doit être codée; Les codages courants sont UTF-8, UTF-16 et UTF-32, les deux derniers étant disponibles à la fois sous les formes little-endian et big-endian, pour un total de 5 codages communs.

En UTF-X, X est la taille en bits de nité de code, chaque point de code est représenté par une ou plusieurs unités de code, en fonction de sa magnitude:

  • UTF-8: 1 à 4 unités de code,
  • UTF-16: 1 ou 2 unités de code,
  • UTF-32: 1 unité de code.

std::string et std::wstring.

  1. N'utilisez pas std::wstring si vous vous souciez de la portabilité (wchar_t n'a que 16 bits sous Windows); utilisez std::u32string à la place (ou std::basic_string<char32_t>).
  2. La représentation en mémoire (std::string ou std::wstring) est indépendante de la représentation sur disque (UTF-8, UTF-16 ou UTF-32). Préparez-vous donc à devoir convertir à la limite. (lire et écrire).
  3. Alors qu'un wchar_t de 32 bits garantit qu'une unité de code représente un point de code complet, il ne représente toujours pas un cluster de graphèmes complet.

Si vous ne faites que lire ou composer des chaînes, vous devriez avoir pas de petits problèmes avec std::string ou std::wstring.

Les problèmes commencent lorsque vous commencez à découper et à couper en dés, puis vous devez faire attention aux (1) limites des points de code (dans UTF-8 ou UTF-16) et (2) des limites des grappes de graphèmes. Le premier peut être manipulé assez facilement par vous-même, le dernier nécessite l’utilisation d’une bibliothèque compatible Unicode.


Choisir std::string ou std::u32string?

Si les performances posent problème, il est probable que std::string obtiendra de meilleurs résultats en raison de sa taille réduite de la mémoire. bien que l'utilisation intensive du chinois puisse changer la donne. Comme toujours, profil.

Si Grapheme Clusters ne pose pas de problème, alors std::u32string présente l'avantage de simplifier les choses: 1 unité de code -> 1 point de code signifie que vous ne pouvez pas scinder accidentellement les points de code et que toutes les fonctions de std::basic_string fonctionnent. de la boîte.

Si vous vous connectez à un logiciel prenant std::string ou char*/char const*, restez-en à std::string pour éviter les conversions en aller-retour. Sinon, ça va être pénible.


UTF-8 dans std::string.

UTF-8 fonctionne plutôt bien dans std::string.

La plupart des opérations sont prêtes à l'emploi, car le codage UTF-8 se synchronise automatiquement et est rétrocompatible avec ASCII.

En raison de la manière dont les points de code sont codés, la recherche d’un point de code ne peut pas accidentellement correspondre au milieu d’un autre point de code:

  • str.find('\n') fonctionne,
  • str.find("...") fonctionne pour faire correspondre octet par octet1,
  • str.find_first_of("\r\n") fonctionne si vous recherchez des caractères ASCII.

De même, regex devrait généralement fonctionner hors de la boîte. Comme une séquence de caractères ("haha") n'est qu'une séquence d'octets ("哈"), les modèles de recherche de base doivent fonctionner immédiatement.

Cependant, méfiez-vous des classes de caractères (telles que [:alphanum:]), car, en fonction de la saveur des expressions rationnelles et de leur implémentation, elles peuvent ou non correspondre aux caractères Unicode.

De même, méfiez-vous de l'application de répéteurs à des "caractères" non-ASCII, "哈?" ne peut considérer que le dernier octet comme optionnel; utilisez des parenthèses pour délimiter clairement la séquence d'octets répétée dans les cas suivants: "(哈)?".

1  Les concepts clés à rechercher sont la normalisation et la compilation; cela affecte toutes les opérations de comparaison. std::string comparera toujours (et donc triera) octet par octet, sans égard aux règles de comparaison spécifiques à une langue ou à un usage. Si vous devez gérer une normalisation/un classement complets, vous avez besoin d'une bibliothèque Unicode complète, telle que ICU.

75
Matthieu M.

std::string et std::wstring doivent tous deux utiliser le codage UTF pour représenter Unicode. Sur macOS en particulier, std::string est UTF-8 (unités de code à 8 bits) et std::wstring est UTF-32 (unités de code à 32 bits); notez que la taille de wchar_t dépend de la plate-forme.

size suit le nombre d'unités de code au lieu du nombre de points de code ou de grappes de graphèmes. (Un point de code est une entité Unicode nommée, dont un ou plusieurs forment un cluster de graphèmes. Les clusters de graphèmes sont les caractères visibles avec lesquels les utilisateurs interagissent, comme les lettres ou les émoticônes.)

Bien que je ne connaisse pas la représentation Unicode du chinois, il est fort possible que, lorsque vous utilisez UTF-32, le nombre d'unités de code soit souvent très proche du nombre de grappes de graphèmes. Évidemment, toutefois, cela revient à utiliser jusqu'à 4 fois plus de mémoire.

La solution la plus précise consiste à utiliser une bibliothèque Unicode, telle que ICU, pour calculer les propriétés Unicode recherchées.

Enfin, les chaînes UTF dans les langages humains qui n'utilisent pas de combinaison de caractères donnent généralement de bons résultats avec find/regex. Je ne suis pas sûr du chinois, mais l'anglais est l'un d'entre eux.

9
zneak

std::string et ses amis sont indépendants de l'encodage. La seule différence entre std::wstring et std::string est que std::wstring utilise wchar_t comme élément individuel et non char. Pour la plupart des compilateurs, ce dernier est en 8 bits. Le premier est censé être assez grand pour contenir n'importe quel caractère unicode, mais en pratique sur certains systèmes, ce n'est pas le cas (le compilateur de Microsoft, par exemple, utilise un type 16 bits). Vous ne pouvez pas stocker UTF-8 dans std::wstring; ce n'est pas ce pour quoi il est conçu. Il est conçu pour être un équivalent de UTF-32 - une chaîne où chaque élément est un seul point de code Unicode.

Si vous souhaitez indexer des chaînes UTF-8 avec un point de code Unicode ou un glyphe unicode composé (ou autre), comptez la longueur d'une chaîne UTF-8 dans des points de code Unicode ou un autre objet Unicode, ou recherchez avec un point de code Unicode, va avoir besoin d'utiliser autre chose que la bibliothèque standard. ICU est l'une des bibliothèques du domaine; il peut y en avoir d'autres.

Il est probablement intéressant de noter que si vous recherchez les caractères ASCII, vous pouvez généralement traiter un flux bytest UTF-8 comme s'il s'agissait octet par octet. Chaque caractère ASCII code la même chose en UTF-8 et en ASCII, et il est garanti que chaque unité multi-octets en UTF-8 n'inclut aucun octet dans la plage ASCII.

8
James Picone

Pensez à passer à C++ 20 et à std::u8string c'est la meilleure chose que nous ayons en 2019 pour conserver l'UTF-8. Il n’existe pas de bibliothèque standard permettant d’accéder à des points de code individuels ou à des grappes de graphèmes, mais au moins votre type est assez fort pour au moins dire que c’est vrai UTF-8.

2
Lyberta