it-swarm.com.de

Wie verwende ich std :: string in UTF-8 in C ++ richtig?

Meine Plattform ist ein Mac und C++ 11 (oder höher). Ich bin ein C++ - Anfänger und arbeite an einem persönlichen Projekt, das Chinesisch und Englisch verarbeitet. UTF-8 ist die bevorzugte Kodierung für dieses Projekt.

Ich habe einige Posts über Stack Overflow gelesen, und viele schlagen vor, std::string Zu verwenden, wenn es um UTF-8 geht, und wchar_t Zu vermeiden, da es derzeit kein char8_t Für UTF-8 gibt.

Keiner von ihnen spricht jedoch darüber, wie man mit Funktionen wie str[i], std::string::size(), std::string::find_first_of() oder std::regex Richtig umgeht, da diese Funktion normalerweise unerwartete Ergebnisse liefert wenn Sie UTF-8 gegenüberstellen.

Soll ich mit std::string Fortfahren oder zu std::wstring Wechseln? Wenn ich bei std::string Bleiben sollte, was ist die beste Vorgehensweise, um mit den oben genannten Problemen umzugehen?

55
stackunderflow

Unicode-Glossar

Unicode ist ein umfangreiches und komplexes Thema. Ich möchte dort nicht zu tief waten, aber ein kurzes Glossar ist notwendig:

  1. Codepunkte: Codepunkte sind die Grundbausteine ​​von Unicode. Ein Codepunkt ist lediglich eine Ganzzahl, die einer Bedeutung zugeordnet ist. Der ganzzahlige Teil passt in 32 Bits (also wirklich in 24 Bits), und die Bedeutung kann ein Buchstabe, ein diakritisches Zeichen, ein Leerzeichen, ein Zeichen, ein Smiley, eine halbe Fahne sein, ... und es kann sogar "das" sein nächster Teil liest von rechts nach links ".
  2. Grapheme-Cluster: Grapheme-Cluster sind Gruppen semantisch verwandter Codepunkte. Ein Flag in Unicode wird beispielsweise durch die Zuordnung von zwei Codepunkten dargestellt. Jedes dieser beiden Elemente hat für sich genommen keine Bedeutung, ist jedoch in einem Grapheme-Cluster zusammengeschlossen und repräsentiert ein Flag. Graphem-Cluster werden in einigen Skripten auch verwendet, um einen Buchstaben mit einem diakritischen Zeichen zu verbinden.

Dies ist das Grundprinzip von Unicode. Die Unterscheidung zwischen Code Point und Grapheme Cluster kann größtenteils übergangen werden, da für die meisten modernen Sprachen jedes "Zeichen" einem einzelnen Code Point zugeordnet ist (es gibt spezielle Akzentformen für häufig verwendete Buchstaben + diakritische Kombinationen). Wenn Sie sich jedoch in Smileys, Flaggen usw. wagen, müssen Sie möglicherweise auf die Unterscheidung achten.


UTF-Grundierung

Dann muss eine Reihe von Unicode-Codepunkten codiert werden. Die gängigen Codierungen sind UTF-8, UTF-16 und UTF-32, wobei die beiden letzteren sowohl in Little-Endian- als auch in Big-Endian-Form vorliegen. Insgesamt gibt es fünf gängige Codierungen.

In UTF-X ist X die Größe in Bits der Code-Einheit, jeder Code-Punkt wird in Abhängigkeit von seiner Größe als eine oder mehrere Code-Einheiten dargestellt:

  • UTF-8: 1 bis 4 Codeeinheiten,
  • UTF-16: 1 oder 2 Codeeinheiten,
  • UTF-32: 1 Code-Einheit.

std::string Und std::wstring.

  1. Verwenden Sie nicht std::wstring, Wenn Sie Portabilität bevorzugen (wchar_t Ist nur 16 Bit unter Windows); Verwenden Sie stattdessen std::u32string (auch bekannt als std::basic_string<char32_t>).
  2. Die Darstellung im Speicher (std::string Oder std::wstring) Ist unabhängig von der Darstellung auf der Festplatte (UTF-8, UTF-16 oder UTF-32). Bereiten Sie sich also darauf vor, um konvertieren zu müssen die Grenze (Lesen und Schreiben).
  3. Während ein 32-Bit-Code wchar_t Sicherstellt, dass eine Code-Unit einen vollständigen Code-Punkt darstellt, stellt sie dennoch keinen vollständigen Grapheme-Cluster dar.

Wenn Sie nur Strings lesen oder komponieren, sollten Sie keine oder nur geringe Probleme mit std::string Oder std::wstring Haben.

Probleme beginnen, wenn Sie anfangen zu schneiden und zu würfeln, dann müssen Sie auf (1) Codepunktgrenzen (in UTF-8 oder UTF-16) und (2) Graphemclustergrenzen achten. Ersteres kann problemlos alleine gehandhabt werden, letzteres erfordert die Verwendung einer Unicode-fähigen Bibliothek.


std::string Oder std::u32string Auswählen?

Wenn die Leistung ein Problem darstellt, ist es wahrscheinlich, dass std::string Aufgrund des geringeren Speicherbedarfs eine bessere Leistung erzielt. wenn auch starker Gebrauch von Chinesisch das Abkommen ändern kann. Profil wie immer.

Wenn Grapheme-Cluster kein Problem darstellen, hat std::u32string Den Vorteil, die Dinge zu vereinfachen: 1 Code-Einheit -> 1 Code-Punkt bedeutet, dass Sie nicht versehentlich Code-Punkte und alle Funktionen von std::basic_string Aufteilen können. Arbeiten Sie aus der Box.

Wenn Sie eine Schnittstelle zu Software herstellen, die std::string Oder char*/char const* Verwendet, bleiben Sie bei std::string, Um Hin- und Her-Konvertierungen zu vermeiden. Sonst wird es weh tun.


UTF-8 in std::string.

UTF-8 funktioniert in std::string Eigentlich ganz gut.

Die meisten Operationen werden sofort ausgeführt, da die UTF-8-Codierung sich selbst synchronisiert und mit ASCII abwärtskompatibel ist.

Aufgrund der Codierung von Codepunkten kann die Suche nach einem Codepunkt nicht versehentlich mit der Mitte eines anderen Codepunkts übereinstimmen:

  • str.find('\n') funktioniert,
  • str.find("...") works für die byteweise Übereinstimmung1,
  • str.find_first_of("\r\n") funktioniert bei Suche nach ASCII Zeichen.

Ebenso sollte regex meistens sofort funktionieren. Da eine Folge von Zeichen ("haha") Nur eine Folge von Bytes ("哈") Ist, sollten grundlegende Suchmuster sofort funktionieren.

Seien Sie jedoch vorsichtig mit Zeichenklassen (wie [:alphanum:]), Da diese je nach Regex-Variante und Implementierung möglicherweise mit Unicode-Zeichen übereinstimmen oder nicht.

Seien Sie auf der Hut, wenn Sie Repeater auf Nicht-ASCII- "Zeichen" anwenden. "哈?" Betrachtet möglicherweise nur das letzte Byte als optional. Verwenden Sie Klammern, um die wiederholte Folge von Bytes in solchen Fällen klar abzugrenzen: "(哈)?".

1  Die Schlüsselkonzepte zum Nachschlagen sind Normalisierung und Kollationierung; Dies betrifft alle Vergleichsoperationen. std::string Vergleicht (und sortiert) immer byteweise, ohne Rücksicht auf Vergleichsregeln, die für eine Sprache oder eine Verwendung spezifisch sind. Wenn Sie eine vollständige Normalisierung/Sortierung durchführen müssen, benötigen Sie eine vollständige Unicode-Bibliothek, z. B. ICU.

75
Matthieu M.

Beide std::string und std::wstring muss UTF-Codierung verwenden, um Unicode darzustellen. Speziell unter macOS kann std::string ist UTF-8 (8-Bit-Code-Einheiten) und std::wstring Ist UTF-32 (32-Bit-Code-Einheiten); Beachten Sie, dass die Größe von wchar_t ist plattformabhängig.

In beiden Fällen verfolgt size die Anzahl der Codeeinheiten anstelle der Anzahl der Codepunkte oder Graphemcluster. (Ein Codepunkt ist eine benannte Unicode-Entität, von der einer oder mehrere einen Graphem-Cluster bilden. Graphem-Cluster sind die sichtbaren Zeichen, mit denen Benutzer interagieren, z. B. Buchstaben oder Emojis.)

Obwohl ich mit der Unicode-Darstellung von Chinesisch nicht vertraut bin, ist es sehr wahrscheinlich, dass die Anzahl der Codeeinheiten bei Verwendung von UTF-32 sehr nahe an der Anzahl der Graphemcluster liegt. Offensichtlich geht dies jedoch zu Lasten des Verbrauchs von bis zu viermal mehr Arbeitsspeicher.

Die genaueste Lösung wäre, eine Unicode-Bibliothek wie ICU zu verwenden, um die gewünschten Unicode-Eigenschaften zu berechnen.

Schließlich eignen sich UTF-Zeichenfolgen in menschlichen Sprachen, die keine kombinierten Zeichen verwenden, gut für find/regex. Bei Chinesisch bin ich mir nicht sicher, aber Englisch ist eine davon.

9
zneak

std::string Und Freunde sind codierungsunabhängig. Der einzige Unterschied zwischen std::wstring Und std::string Besteht darin, dass std::wstringwchar_t Als einzelnes Element verwendet, nicht char. Für die meisten Compiler ist letzteres 8-Bit. Ersteres sollte groß genug sein, um Unicode-Zeichen aufzunehmen, in der Praxis ist dies jedoch auf einigen Systemen nicht der Fall (der Compiler von Microsoft verwendet beispielsweise einen 16-Bit-Typ). Sie können UTF-8 nicht in std::wstring Speichern. dafür ist es nicht ausgelegt. Es ist so konzipiert, dass es UTF-32 entspricht - eine Zeichenfolge, bei der jedes Element ein einzelner Unicode-Codepunkt ist.

Wenn Sie UTF-8-Zeichenfolgen nach Unicode-Codepunkt oder zusammengesetztem Unicode-Glyphen (oder einem anderen Element) indizieren möchten, zählen Sie die Länge einer UTF-8-Zeichenfolge in Unicode-Codepunkten oder einem anderen Unicode-Objekt oder suchen Sie nach Unicode-Codepunkt Sie müssen etwas anderes als die Standardbibliothek verwenden. ICU ist eine der Bibliotheken auf dem Gebiet; es kann andere geben.

Es ist wahrscheinlich erwähnenswert, dass Sie bei der Suche nach ASCII Zeichen einen UTF-8-Bytestream meistens so behandeln können, als ob er byteweise wäre. Jedes ASCII -Zeichen codiert in UTF-8 dasselbe wie in ASCII, und für jede Multibyte-Einheit in UTF-8 wird garantiert, dass sie keine Bytes im Bereich ASCII enthält.

8
James Picone

Erwägen Sie ein Upgrade auf C++ 20 und std::u8string das ist das Beste, was wir ab 2019 haben, um UTF-8 zu halten. Es gibt keine Standardbibliotheksfunktionen für den Zugriff auf einzelne Codepunkte oder Graphemcluster, aber Ihr Typ ist stark genug, um zumindest zu behaupten, dass es sich um UTF-8 handelt.

2
Lyberta