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?
Unicode est un sujet vaste et complexe. Je ne souhaite pas trop y aller, cependant un glossaire rapide est nécessaire:
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.
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:
std::string
et std::wstring
.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>
).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).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.
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.
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.
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.
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.
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.