Ich habe gelesen und gehört, dass C ++ 11 Unicode unterstützt. Ein paar Fragen dazu:
- Wie gut unterstützt die C ++ - Standardbibliothek Unicode?
- Tut
std::string
was es soll? - Wie benutze ich es?
- Wo liegen mögliche Probleme?
Ich habe gelesen und gehört, dass C ++ 11 Unicode unterstützt. Ein paar Fragen dazu:
std::string
was es soll?Antworten:
Wie gut unterstützt die C ++ - Standardbibliothek Unicode?
Fürchterlich.
Ein kurzer Scan durch die Bibliothekseinrichtungen, die möglicherweise Unicode-Unterstützung bieten, gibt mir folgende Liste:
Ich denke, alle bis auf den ersten bieten schreckliche Unterstützung. Ich werde nach einem kurzen Umweg durch Ihre anderen Fragen ausführlicher darauf zurückkommen.
Tut
std::string
was es soll?
Ja. Nach dem C ++ - Standard sollten dies std::string
und seine Geschwister Folgendes tun:
Die Klassenvorlage
basic_string
beschreibt Objekte, die eine Sequenz speichern können, die aus einer variierenden Anzahl beliebiger char-ähnlicher Objekte besteht, wobei sich das erste Element der Sequenz an Position Null befindet.
Nun, std::string
macht das gut. Bietet das Unicode-spezifische Funktionen? Nein.
Sollte es? Wahrscheinlich nicht. std::string
ist gut als eine Folge von char
Objekten. Das ist nützlich; Das einzige Ärgernis ist, dass es sich um eine sehr einfache Ansicht von Text handelt und Standard-C ++ keine übergeordnete Ansicht bietet.
Wie benutze ich es?
Verwenden Sie es als eine Folge von char
Objekten; so zu tun, als wäre es etwas anderes, das mit Schmerzen enden muss.
Wo liegen mögliche Probleme?
Überall? Mal schauen...
Strings Bibliothek
Die Zeichenfolgenbibliothek bietet uns basic_string
lediglich eine Folge dessen, was der Standard "char-ähnliche Objekte" nennt. Ich nenne sie Codeeinheiten. Wenn Sie eine allgemeine Textansicht wünschen, ist dies nicht das, wonach Sie suchen. Dies ist eine Ansicht von Text, der für die Serialisierung / Deserialisierung / Speicherung geeignet ist.
Es enthält auch einige Tools aus der C-Bibliothek, mit denen die Lücke zwischen der engen Welt und der Unicode-Welt geschlossen werden kann: c16rtomb
/ mbrtoc16
und c32rtomb
/ mbrtoc32
.
Lokalisierungsbibliothek
Die Lokalisierungsbibliothek glaubt immer noch, dass eines dieser "char-ähnlichen Objekte" einem "Zeichen" entspricht. Dies ist natürlich albern und macht es unmöglich, viele Dinge über eine kleine Teilmenge von Unicode wie ASCII hinaus richtig zum Laufen zu bringen.
Überlegen Sie beispielsweise, was der Standard im <locale>
Header als "Convenience-Schnittstellen" bezeichnet :
template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
Wie erwarten Sie, dass eine dieser Funktionen beispielsweise U + 1F34C ʙᴀɴᴀɴᴀ wie in u8"🍌"
oder richtig kategorisiert u8"\U0001F34C"
? Es wird auf keinen Fall jemals funktionieren, da diese Funktionen nur eine Codeeinheit als Eingabe verwenden.
Dies könnte mit einem geeigneten Gebietsschema funktionieren, wenn Sie char32_t
nur Folgendes verwenden : U'\U0001F34C'
ist eine einzelne Codeeinheit in UTF-32.
Das bedeutet jedoch, dass nach wie vor nur Sie die einfachen Gehäuse Transformationen bekommen toupper
und tolower
, die, zum Beispiel, ist nicht gut genug für einige deutschen Gegenden: „ß“ uppercases auf „SS“ ☦ sondern toupper
kann nur eine Rückzeichencodeeinheit.
Als nächstes wstring_convert
/ wbuffer_convert
und die Facetten der Standardcodekonvertierung.
wstring_convert
wird verwendet, um zwischen Zeichenfolgen in einer bestimmten Codierung in Zeichenfolgen in einer anderen bestimmten Codierung umzuwandeln. An dieser Transformation sind zwei Zeichenfolgentypen beteiligt, die der Standard als Byte-Zeichenfolge und als breite Zeichenfolge bezeichnet. Da diese Begriffe wirklich irreführend sind, bevorzuge ich die Verwendung von "serialisiert" bzw. "deserialisiert" anstelle von †.
Die zu konvertierenden Codierungen werden von einem Codecvt (einer Codekonvertierungsfacette) festgelegt, der als Argument für den Vorlagentyp übergeben wird wstring_convert
.
wbuffer_convert
führt eine ähnliche Funktion aus, jedoch als breiter deserialisierter Stream-Puffer, der einen Byte- serialisierten Stream-Puffer umschließt. Alle E / A werden über den zugrunde liegenden Byte- serialisierten Stream-Puffer mit Konvertierungen zu und von den durch das Codecvt-Argument angegebenen Codierungen ausgeführt. Das Schreiben wird in diesen Puffer serialisiert und dann aus ihm geschrieben, und das Lesen liest in den Puffer und deserialisiert dann aus ihm.
Der Standard bietet einige codecvt Klassenvorlagen zur Verwendung mit diesen Einrichtungen: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
, und einige codecvt
Spezialisierungen. Zusammen bieten diese Standardfacetten alle folgenden Konvertierungen. (Hinweis: In der folgenden Liste ist die Codierung links immer die serialisierte Zeichenfolge / Streambuf und die Codierung rechts immer die deserialisierte Zeichenfolge / Streambuf. Der Standard erlaubt Konvertierungen in beide Richtungen.)
codecvt_utf8<char16_t>
und codecvt_utf8<wchar_t>
wo sizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
und codecvt_utf8<wchar_t>
wobei sizeof(wchar_t) == 4
;codecvt_utf16<char16_t>
und codecvt_utf16<wchar_t>
wo sizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
und codecvt_utf16<wchar_t>
wo sizeof(wchar_t) == 4
;codecvt_utf8_utf16<char16_t>
, codecvt<char16_t, char, mbstate_t>
und codecvt_utf8_utf16<wchar_t>
wobei sizeof(wchar_t) == 2
;codecvt<wchar_t, char_t, mbstate_t>
codecvt<char, char, mbstate_t>
.Einige davon sind nützlich, aber hier gibt es viele unangenehme Dinge.
Zunächst einmal - heiliger hoher Ersatz! Dieses Namensschema ist chaotisch.
Dann gibt es viel UCS-2-Unterstützung. UCS-2 ist eine Codierung aus Unicode 1.0, die 1996 ersetzt wurde, da sie nur die mehrsprachige Grundebene unterstützt. Warum das Komitee es für wünschenswert hielt, sich auf eine Kodierung zu konzentrieren, die vor über 20 Jahren abgelöst wurde, weiß ich nicht ‡. Es ist nicht so, dass die Unterstützung für mehr Codierungen schlecht oder so ist, aber UCS-2 wird hier zu oft angezeigt.
Ich würde sagen, dass dies char16_t
offensichtlich zum Speichern von UTF-16-Codeeinheiten gedacht ist. Dies ist jedoch ein Teil des Standards, der anders denkt. codecvt_utf8<char16_t>
hat nichts mit UTF-16 zu tun. Zum Beispiel wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
wird kompilieren in Ordnung, aber fehl bedingungslos: der Eingang wird als UCS-2 - String behandelt werden u"\xD83C\xDF4C"
, die in UTF-8 konvertiert werden können , da UTF-8 nicht einen beliebigen Wert im Bereich 0xD800-0xDFFF kodieren kann.
Auf der UCS-2-Front gibt es noch keine Möglichkeit, mit diesen Facetten aus einem UTF-16-Byte-Stream in einen UTF-16-String zu lesen. Wenn Sie eine Folge von UTF-16-Bytes haben, können Sie diese nicht in eine Zeichenfolge von deserialisieren char16_t
. Dies ist überraschend, da es sich mehr oder weniger um eine Identitätskonvertierung handelt. Noch überraschender ist jedoch die Tatsache, dass die Deserialisierung von einem UTF-16-Stream in einen UCS-2-String mit unterstützt wird codecvt_utf16<char16_t>
, was eigentlich eine verlustbehaftete Konvertierung ist.
Die UTF-16-as-Byte-Unterstützung ist jedoch recht gut: Sie unterstützt das Erkennen von Endianess aus einer Stückliste oder das explizite Auswählen im Code. Es unterstützt auch die Erzeugung von Ausgaben mit und ohne Stückliste.
Es fehlen einige weitere interessante Konvertierungsmöglichkeiten. Es gibt keine Möglichkeit, von einem UTF-16-Byte-Stream oder einer Zeichenfolge in eine UTF-8-Zeichenfolge zu deserialisieren, da UTF-8 niemals als deserialisierte Form unterstützt wird.
Und hier ist die enge / weite Welt völlig getrennt von der UTF / UCS-Welt. Es gibt keine Konvertierungen zwischen den schmalen / breiten Codierungen im alten Stil und den Unicode-Codierungen.
Eingabe- / Ausgabebibliothek
Die E / A-Bibliothek kann zum Lesen und Schreiben von Text in Unicode-Codierungen unter Verwendung der oben beschriebenen wstring_convert
und Funktionen verwendet wbuffer_convert
werden. Ich glaube nicht, dass dieser Teil der Standardbibliothek noch viel mehr unterstützen müsste.
Bibliothek für reguläre Ausdrücke
Ich habe bereits zuvor Probleme mit C ++ - Regexen und Unicode on Stack Overflow erläutert . Ich werde hier nicht alle diese Punkte wiederholen, sondern lediglich angeben, dass C ++ - Regexes keine Unicode-Unterstützung der Stufe 1 bieten. Dies ist das absolute Minimum, um sie nutzbar zu machen, ohne überall UTF-32 zu verwenden.
Das ist es?
Ja das ist es. Das ist die vorhandene Funktionalität. Es gibt viele Unicode-Funktionen, die wie Normalisierungs- oder Textsegmentierungsalgorithmen nirgends zu sehen sind.
U + 1F4A9 . Gibt es eine Möglichkeit, eine bessere Unicode-Unterstützung in C ++ zu erhalten?
Die üblichen Verdächtigen: ICU und Boost.Locale .
† Eine Byte-Zeichenfolge ist nicht überraschend eine Zeichenfolge von Bytes, dh char
Objekten. Im Gegensatz zu einem breiten Zeichenfolgenliteral , das immer ein Array von wchar_t
Objekten ist, ist eine "breite Zeichenfolge" in diesem Zusammenhang jedoch nicht unbedingt eine Zeichenfolge von wchar_t
Objekten. Tatsächlich definiert der Standard niemals explizit, was eine "breite Zeichenfolge" bedeutet, so dass wir die Bedeutung der Verwendung erraten müssen. Da die Standardterminologie schlampig und verwirrend ist, verwende ich meine eigene im Namen der Klarheit.
Codierungen wie UTF-16 können als Sequenzen von gespeichert werden char16_t
, die dann keine Endianness haben; oder sie können als Folgen von Bytes gespeichert werden, die Endianness haben (jedes aufeinanderfolgende Bytepaar kann char16_t
je nach Endianness einen anderen Wert darstellen). Der Standard unterstützt beide Formen. Eine Folge von char16_t
ist für die interne Manipulation im Programm nützlicher. Eine Folge von Bytes ist der Weg, solche Zeichenfolgen mit der Außenwelt auszutauschen. Die Begriffe, die ich anstelle von "Byte" und "Wide" verwenden werde, sind daher "serialisiert" und "deserialisiert".
‡ Wenn Sie "aber Windows!" halte dein 🐎🐎 . Alle Windows-Versionen seit Windows 2000 verwenden UTF-16.
☦ Ja, ich kenne das große Eszett (ẞ), aber selbst wenn Sie über Nacht alle deutschen Gebietsschemas ändern würden, um Großbuchstaben in ẞ zu setzen, gibt es noch viele andere Fälle, in denen dies fehlschlagen würde. Versuchen Sie, U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ in Großbuchstaben zu schreiben. Es gibt kein ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; es werden nur Großbuchstaben auf zwei Fs gesetzt. Oder U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴊ ᴄᴀʀᴏɴ; Es gibt kein vorkomponiertes Kapital. Es werden nur Großbuchstaben zu einem Großbuchstaben J und einem kombinierten Caron verwendet.
Unicode wird von der Standardbibliothek nicht unterstützt (für eine vernünftige Bedeutung von unterstützt).
std::string
ist nicht besser als std::vector<char>
: Unicode (oder jede andere Darstellung / Codierung) wird nicht wahrgenommen und der Inhalt wird einfach als Byte- Blob behandelt .
Wenn Sie nur Blobs speichern und verketten müssen , funktioniert dies ziemlich gut. Sobald Sie jedoch die Unicode-Funktionalität (Anzahl der Codepunkte , Anzahl der Grapheme usw.) wünschen, haben Sie kein Glück mehr.
Die einzige umfassende Bibliothek, die mir dafür bekannt ist, ist die Intensivstation . Die C ++ - Schnittstelle wurde jedoch von der Java-Schnittstelle abgeleitet, sodass sie keineswegs idiomatisch ist.
Sie können UTF-8 sicher in einem std::string
(oder in einem char[]
oder char*
) speichern , da ein Unicode-NUL (U + 0000) in UTF-8 ein Null-Byte ist und dies der einzige Weg ist, eine Null zu sein Byte kann in UTF-8 auftreten. Daher werden Ihre UTF-8-Zeichenfolgen gemäß allen C- und C ++ - Zeichenfolgenfunktionen ordnungsgemäß terminiert, und Sie können sie mit C ++ - iostreams (einschließlich std::cout
und std::cerr
, solange Ihr Gebietsschema UTF-8 ist) herumschleudern.
Was Sie std::string
für UTF-8 nicht tun können, ist die Länge in Codepunkten abzurufen. std::string::size()
zeigt Ihnen die Zeichenfolgenlänge in Bytes an , die nur der Anzahl der Codepunkte entspricht, wenn Sie sich in der ASCII-Teilmenge von UTF-8 befinden.
Wenn Sie UTF-8-Zeichenfolgen auf Codepunktebene bearbeiten müssen (dh nicht nur speichern und drucken müssen) oder wenn Sie mit UTF-16 arbeiten, das wahrscheinlich viele interne Nullbytes enthält, müssen Sie dies untersuchen die breiten Zeichenkettentypen.
std::string
kann mit eingebetteten Nullen in iostreams geworfen werden.
c_str()
weil es size()
immer noch funktioniert. Nur defekte APIs (dh solche, die nicht wie die meisten in der C-Welt mit eingebetteten Nullen umgehen können) brechen.
c_str()
weil c_str()
die Daten als nullterminierte C-Zeichenfolge zurückgegeben werden sollen. Dies ist unmöglich, da C-Zeichenfolgen keine eingebetteten Nullen enthalten können.
c_str()
Jetzt wird einfach das Gleiche zurückgegeben wie data()
, dh alles. APIs, die eine Größe annehmen, können diese verbrauchen. APIs, die dies nicht tun, können dies nicht.
c_str()
der sicherstellt , dass auf das Ergebnis ein NUL-Zeichen-ähnliches Objekt folgt, und ich glaube nicht data()
. Nein, sieht so aus, als würde das data()
jetzt auch so sein. (Dies ist natürlich nicht erforderlich für APIs, die die Größe verbrauchen, anstatt sie aus einer Terminatorsuche abzuleiten.)
C ++ 11 verfügt über einige neue Literal-String-Typen für Unicode.
Leider ist die Unterstützung in der Standardbibliothek für ungleichmäßige Codierungen (wie UTF-8) immer noch schlecht. Zum Beispiel gibt es keine gute Möglichkeit, die Länge (in Codepunkten) eines UTF-8-Strings zu ermitteln.
std::string
kann halten einen UTF-8 - String , ohne Problem, aber die zB length
Methode gibt die Anzahl der Bytes in der Zeichenfolge und nicht die Anzahl der Codepunkte.
ñ
als 'LATEINISCHER KLEINBUCHSTABE N MIT TILD' (U + 00F1) (das ist ein Codepunkt) oder 'LATEINISCHER KLEINBUCHSTABE N' ( U + 006E) gefolgt von 'COMBINING TILDE' (U + 0303), zwei Codepunkten.
LATIN SMALL LETTER N'
== berücksichtigt oder nicht (U+006E) followed by 'COMBINING TILDE' (U+0303)
.
Es gibt jedoch eine ziemlich nützliche Bibliothek namens tiny-utf8 , die im Grunde ein Drop-In-Ersatz für std::string
/ ist std::wstring
. Ziel ist es, die Lücke der noch fehlenden utf8-string-Containerklasse zu schließen.
Dies ist möglicherweise die bequemste Art, mit utf8-Zeichenfolgen umzugehen (dh ohne Unicode-Normalisierung und ähnliches). Sie können Codepunkte bequem bearbeiten , während Ihre Zeichenfolge in Lauflängen-codierten char
s codiert bleibt .