In che modo Unicode è supportato in C ++ 11?


183

Ho letto e sentito che C ++ 11 supporta Unicode. Alcune domande al riguardo:

  • In che misura la libreria standard C ++ supporta Unicode?
  • Fa std::stringquello che dovrebbe?
  • Come lo uso?
  • Dove sono i potenziali problemi?

19
"Lo std :: string fa quello che dovrebbe?" Cosa pensi che dovrebbe fare?
R. Martinho Fernandes,

2
Uso utfcpp.sourceforge.net per le mie esigenze utf8. È un semplice file di intestazione che fornisce iteratori per stringhe unicode.
fscan,

2
std :: string dovrebbe memorizzare i byte, cioè la sequenza di unità di codice della codifica UTF-8. Sì, lo fa proprio dall'inizio. utf8everywhere.org
Pavel Radzivilovsky,

3
I maggiori problemi potenziali con il supporto Unicode risiedono in Unicode e nel suo utilizzo nella stessa tecnologia informatica. Unicode non è adatto (e non progettato) per quello per cui è utilizzato. Unicode è progettato per riprodurre ogni possibile glifo che è stato scritto da qualche parte da qualcuno, in qualche momento con ogni sfumatura improbabile e pedante possibile, inclusi 3 o 4 significati diversi e 3 o 4 modi diversi di comporre lo stesso glifo. Non è pensato per essere utile per essere usato per il linguaggio di tutti i giorni, e non è pensato per essere applicabile o per essere facilmente o inequivocabilmente elaborato.
Damon,

11
Sì, è progettato per essere utilizzato per il linguaggio di tutti i giorni. Almeno il mio. E molto probabilmente anche il tuo. Si scopre solo che l'elaborazione del testo umano in modo generale è un compito molto difficile. Non è nemmeno possibile definire in modo inequivocabile cosa sia un personaggio. La riproduzione generale dei glifi non fa nemmeno parte della Carta Unicode.
Jean-Denis Muys,

Risposte:


267

In che misura la libreria standard C ++ supporta Unicode?

Terribilmente.

Una rapida scansione delle strutture della libreria che potrebbe fornire il supporto Unicode mi dà questo elenco:

  • Libreria di stringhe
  • Biblioteca di localizzazione
  • Libreria di input / output
  • Biblioteca di espressioni regolari

Penso che tutti tranne il primo forniscano un supporto terribile. Ci tornerò più in dettaglio dopo una rapida deviazione attraverso le altre tue domande.

Fa std::stringquello che dovrebbe?

Sì. Secondo lo standard C ++, questo è ciò std::stringche i suoi fratelli dovrebbero fare:

Il modello di classe basic_stringdescrive oggetti che possono memorizzare una sequenza composta da un numero variabile di oggetti arbitrari simili a caratteri con il primo elemento della sequenza nella posizione zero.

Bene, std::stringva bene. Ciò fornisce funzionalità specifiche di Unicode? No.

Dovrebbe? Probabilmente no. std::stringva bene come una sequenza di charoggetti. Questo è utile; l'unico fastidio è che si tratta di una visione di livello molto basso del testo e che il C ++ standard non ne fornisce una di livello superiore.

Come lo uso?

Usalo come una sequenza di charoggetti; fingendo che qualcos'altro è destinato a finire nel dolore.

Dove sono i potenziali problemi?

Dappertutto? Vediamo...

Libreria di stringhe

La libreria di stringhe ci fornisce basic_string, che è semplicemente una sequenza di ciò che lo standard chiama "oggetti char-like". Le chiamo unità di codice. Se vuoi una visione di alto livello del testo, questo non è ciò che stai cercando. Questa è una vista di testo adatta per serializzazione / deserializzazione / archiviazione.

Fornisce inoltre alcuni strumenti della libreria C che possono essere utilizzati per colmare il divario tra il mondo stretto e il mondo Unicode: c16rtomb/ mbrtoc16e c32rtomb/ mbrtoc32.

Biblioteca di localizzazione

La biblioteca di localizzazione ritiene ancora che uno di quegli "oggetti simili a caratteri" sia uguale a un "carattere". Questo è ovviamente sciocco e rende impossibile far funzionare correttamente molte cose oltre qualche piccolo sottoinsieme di Unicode come ASCII.

Considera, ad esempio, ciò che lo standard chiama "interfacce di convenienza" <locale>nell'intestazione:

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);
// ...

Come ti aspetti che una di queste funzioni classifichi correttamente, diciamo, U + 1F34C ʙᴀɴᴀɴᴀ, come in u8"🍌"o u8"\U0001F34C"? Non funzionerà mai, perché quelle funzioni accettano solo un'unità di codice come input.

Questo potrebbe funzionare con un'impostazione internazionale appropriata se hai usato char32_tsolo: U'\U0001F34C'è una singola unità di codice in UTF-32.

Tuttavia, ciò significa comunque che si ottengono solo le semplici trasformazioni di involucro con touppere tolower, che, ad esempio, non sono abbastanza buone per alcune versioni tedesche: "ß" maiuscolo su "SS" ☦ ma toupperpuò restituire solo un'unità di codice carattere .

Successivamente, wstring_convert/ wbuffer_converte le sfaccettature di conversione del codice standard.

wstring_convertviene usato per convertire tra le stringhe in una data codifica in stringhe in un'altra data codifica. Esistono due tipi di stringa coinvolti in questa trasformazione, che lo standard chiama una stringa di byte e una stringa ampia. Dato che questi termini sono davvero fuorvianti, preferisco usare "serializzato" e "deserializzato", invece, invece †.

Le codifiche tra cui convertire sono decise da un codecvt (un facet di conversione del codice) passato come argomento del tipo di modello wstring_convert.

wbuffer_convertsvolge una funzione simile ma come un ampio buffer di flusso deserializzato che avvolge un buffer di flusso serializzato in byte . Qualsiasi I / O viene eseguito attraverso il buffer di flusso serializzato byte sottostante con conversioni da e verso le codifiche fornite dall'argomento codecvt. La scrittura serializza in quel buffer, quindi scrive da esso e la lettura legge nel buffer e quindi si deserializza da esso.

Lo standard prevede alcuni modelli di classe codecvt per l'uso con queste strutture: codecvt_utf8, codecvt_utf16, codecvt_utf8_utf16, e alcune codecvtspecializzazioni. Insieme, queste sfaccettature standard forniscono tutte le seguenti conversioni. (Nota: nel seguente elenco, la codifica a sinistra è sempre la stringa / streambuf serializzata e la codifica a destra è sempre la stringa / streambuf deserializzata; lo standard consente le conversioni in entrambe le direzioni).

  • UTF-8 ↔ UCS-2 con codecvt_utf8<char16_t>e codecvt_utf8<wchar_t>dove sizeof(wchar_t) == 2;
  • UTF-8 ↔ UTF-32 con codecvt_utf8<char32_t>, codecvt<char32_t, char, mbstate_t>e codecvt_utf8<wchar_t>dove sizeof(wchar_t) == 4;
  • UTF-16 ↔ UCS-2 con codecvt_utf16<char16_t>e codecvt_utf16<wchar_t>dove sizeof(wchar_t) == 2;
  • UTF-16 ↔ UTF-32 con codecvt_utf16<char32_t>e codecvt_utf16<wchar_t>dove sizeof(wchar_t) == 4;
  • UTF-8 ↔ UTF-16 con codecvt_utf8_utf16<char16_t>, codecvt<char16_t, char, mbstate_t>e codecvt_utf8_utf16<wchar_t>dove sizeof(wchar_t) == 2;
  • stretto ↔ largo con codecvt<wchar_t, char_t, mbstate_t>
  • no-op con codecvt<char, char, mbstate_t>.

Molti di questi sono utili, ma ci sono molte cose imbarazzanti qui.

Prima di tutto, santo surrogato! quello schema di denominazione è disordinato.

Quindi, c'è molto supporto UCS-2. UCS-2 è una codifica di Unicode 1.0 che è stata sostituita nel 1996 perché supporta solo il piano multilingue di base. Non so perché il comitato abbia ritenuto opportuno concentrarsi su una codifica sostituita più di 20 anni fa. Non è che il supporto per più codifiche sia negativo o altro, ma UCS-2 appare troppo spesso qui.

Direi che char16_tè ovviamente pensato per la memorizzazione di unità di codice UTF-16. Tuttavia, questa è una parte dello standard che la pensa diversamente. codecvt_utf8<char16_t>non ha nulla a che fare con UTF-16. Ad esempio, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")verrà compilato correttamente, ma fallirà incondizionatamente: l'input verrà trattato come la stringa UCS-2 u"\xD83C\xDF4C", che non può essere convertita in UTF-8 perché UTF-8 non può codificare alcun valore nell'intervallo 0xD800-0xDFFF.

Sempre sul fronte UCS-2, non c'è modo di leggere da un flusso di byte UTF-16 in una stringa UTF-16 con queste sfaccettature. Se hai una sequenza di byte UTF-16 non puoi deserializzarla in una stringa di char16_t. Questo è sorprendente, perché è più o meno una conversione di identità. Ancora più sorprendente, tuttavia, è il fatto che esiste il supporto per la deserializzazione da un flusso UTF-16 in una stringa UCS-2 con codecvt_utf16<char16_t>, che in realtà è una conversione con perdita.

Il supporto UTF-16-as-bytes è abbastanza buono, tuttavia: supporta il rilevamento di endianess da una DBA o la selezione esplicita nel codice. Supporta anche la produzione di output con e senza una distinta base.

Vi sono alcune possibilità di conversione più interessanti assenti. Non è possibile deserializzare da uno stream o una stringa di byte UTF-16 in una stringa UTF-8, poiché UTF-8 non è mai supportato come modulo deserializzato.

E qui il mondo stretto / largo è completamente separato dal mondo UTF / UCS. Non ci sono conversioni tra le codifiche restrittive / ampie vecchio stile e le codifiche Unicode.

Libreria di input / output

La libreria I / O può essere utilizzata per leggere e scrivere testo nelle codifiche Unicode utilizzando le funzioni wstring_converte wbuffer_convertdescritte sopra. Non credo ci sia molto altro che dovrebbe essere supportato da questa parte della libreria standard.

Biblioteca di espressioni regolari

In precedenza ho spiegato i problemi con le regex C ++ e Unicode su Stack Overflow. Non ripeterò tutti questi punti qui, ma affermerò semplicemente che le regex C ++ non hanno il supporto Unicode di livello 1, che è il minimo indispensabile per renderle utilizzabili senza ricorrere all'uso di UTF-32 ovunque.

Questo è tutto?

Sì è quello. Questa è la funzionalità esistente. Esistono molte funzionalità Unicode che non si vedono da nessuna parte come algoritmi di normalizzazione o segmentazione del testo.

U + 1F4A9 . Esiste un modo per ottenere un supporto Unicode migliore in C ++?

I soliti sospetti: ICU e Boost.Locale .


† Una stringa di byte è, ovviamente, una stringa di byte, ovvero charoggetti. Tuttavia, a differenza di una stringa larga letterale , che è sempre una matrice di wchar_toggetti, una "stringa larga" in questo contesto non è necessariamente una stringa di wchar_toggetti. In effetti, lo standard non definisce mai in modo esplicito cosa significhi una "stringa larga", quindi non ci resta che indovinare il significato dall'uso. Poiché la terminologia standard è sciatta e confusa, ne uso la mia, in nome della chiarezza.

Codifiche come UTF-16 possono essere memorizzate come sequenze di char16_t, che quindi non hanno endianness; oppure possono essere memorizzati come sequenze di byte, che hanno endianness (ogni coppia consecutiva di byte può rappresentare un char16_tvalore diverso a seconda dell'endianness). Lo standard supporta entrambe queste forme. Una sequenza di char16_tè più utile per la manipolazione interna nel programma. Una sequenza di byte è il modo di scambiare tali stringhe con il mondo esterno. I termini che userò al posto di "byte" e "wide" sono quindi "serializzati" e "deserializzati".

‡ Se stai per dire "ma Windows!" tieni premuto il tuo 🐎🐎 . Tutte le versioni di Windows da Windows 2000 utilizzano UTF-16.

☦ Sì, conosco il großes Eszett (ẞ), ma anche se dovessi cambiare tutti i locali tedeschi durante la notte per avere ß maiuscolo in ẞ, ci sono ancora molti altri casi in cui questo fallirebbe. Prova a mettere in maiuscolo U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. Non esiste ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; diventa maiuscolo fino a due F. Oppure U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; non c'è capitale precomposto; diventa maiuscolo solo con una J maiuscola e un caron combinato.


26
Più ne leggo, più ho la sensazione di non capire nulla di tutto ciò. Ho letto la maggior parte di queste cose un paio di mesi fa e mi sento ancora come se stessi scoprendo di nuovo tutto da capo ... Per renderlo semplice per il mio povero cervello che ora fa un po 'male, tutti questi consigli su utf8 ovunque sono ancora validi, giusto? Se "solo" voglio che i miei utenti siano in grado di aprire e scrivere file indipendentemente dalle impostazioni di sistema, posso chiedere loro il nome del file, archiviarlo in una stringa std :: string e tutto dovrebbe funzionare correttamente, anche su Windows? Mi dispiace chiederlo (di nuovo) ...
Uflex il

5
@Uflex Tutto ciò che puoi veramente fare con std :: string è trattarlo come un BLOB binario. In una corretta implementazione Unicode né l'interno (perché è nascosto in profondità nei dettagli dell'implementazione) né la codifica esterna conta (beh, in qualche modo, devi ancora avere encoder / decoder disponibili).
Cat Plus Plus,

3
@Uflex forse. Non so se seguire i consigli che non capisci sia una buona idea.
R. Martinho Fernandes,

1
Esiste una proposta per il supporto Unicode in C ++ 2014/17. Tuttavia, questo è 1, forse 4 anni di distanza e di scarsa utilità ora. open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html
graham.reeds

20
@ graham.reeds haha, grazie, ma ne ero consapevole. Controlla la sezione "Ringraziamenti";)
R. Martinho Fernandes il

40

Unicode non è supportato dalla Libreria standard (per qualsiasi significato ragionevole di supportato).

std::stringnon è meglio di std::vector<char>: è completamente ignaro di Unicode (o di qualsiasi altra rappresentazione / codifica) e tratta semplicemente il suo contenuto come un blocco di byte.

Se avete solo bisogno di memorizzare e catenate blob , funziona piuttosto bene; ma non appena desideri la funzionalità Unicode (numero di punti di codice , numero di grafemi ecc.) sei sfortunato.

L'unica libreria completa che conosco per questo è ICU . L'interfaccia C ++ è stata derivata da quella Java, quindi è tutt'altro che idiomatica.


2
Che ne dici di Boost.Locale ?
Uflex,

11
@Uflex: dalla pagina che hai collegato Per raggiungere questo obiettivo Boost.Locale utilizza la libreria Unicode e Localization all'avanguardia: ICU - International Components for Unicode.
Matthieu M.

1
Boost.Locale supporta altri back-end non-ICU, vedi qui: boost.org/doc/libs/1_53_0/libs/locale/doc/html/…
Superfly Jon,

@SuperflyJon: Vero, ma secondo quella stessa pagina, il supporto per Unicode dei back-end non ICU è "fortemente limitato".
Matthieu M.,

24

È possibile archiviare in modo sicuro UTF-8 in un std::string(o in un char[]o char*, del resto), a causa del fatto che un Unicode NUL (U + 0000) è un byte null in UTF-8 e che questo è l'unico modo un null byte può verificarsi in UTF-8. Pertanto, le stringhe UTF-8 verranno terminate correttamente in base a tutte le funzioni di stringa C e C ++ e sarà possibile spostarle con iostreams C ++ (incluso std::coute std::cerr, purché la propria locale sia UTF-8).

Quello che non puoi fare con std::stringUTF-8 è ottenere la lunghezza in punti di codice. std::string::size()ti dirà la lunghezza della stringa in byte , che è uguale al numero di punti di codice solo all'interno del sottoinsieme ASCII di UTF-8.

Se devi operare su stringhe UTF-8 a livello di punto di codice (cioè non solo memorizzarle e stamparle) o se hai a che fare con UTF-16, che probabilmente avrà molti byte null interni, devi esaminare i tipi di stringa di caratteri ampi.


3
std::stringpuò essere gettato in iostreams con valori null incorporati bene.
R. Martinho Fernandes,

3
È totalmente inteso. Non si rompe c_str()affatto perché size()funziona ancora. Solo le API rotte (ovvero quelle che non sono in grado di gestire valori null incorporati come la maggior parte del mondo C) si interrompono.
R. Martinho Fernandes,

1
I null incorporati si rompono c_str()perché c_str()si suppone che restituiscano i dati come una stringa C con terminazione null --- il che è impossibile, a causa del fatto che le stringhe C non possono avere null incorporati.
Uckelman,

4
Non più. c_str()ora restituisce semplicemente lo stesso data(), cioè tutto. Le API che assumono una dimensione possono utilizzarla. API che non lo fanno, non possono.
R. Martinho Fernandes,

6
Con la leggera differenza che c_str()assicura che il risultato sia seguito da un oggetto NUL simile a un carattere, e non credo che lo data()faccia. No, sembra data()che anche adesso lo faccia. (Naturalmente, questo non è necessario per le API che consumano la dimensione invece di inferirla da una ricerca terminatore)
Ben Voigt,

8

C ++ 11 ha un paio di nuovi tipi di stringhe letterali per Unicode.

Sfortunatamente il supporto nella libreria standard per codifiche non uniformi (come UTF-8) è ancora scarso. Ad esempio, non esiste un modo carino per ottenere la lunghezza (in punti di codice) di una stringa UTF-8.


Quindi dobbiamo ancora usare std :: wstring per i nomi dei file se vogliamo supportare lingue non latine? Perché i nuovi letterali di stringhe non aiutano davvero qui poiché la stringa di solito proviene dall'utente ...
Uflex

7
@Uflex std::stringpuò contenere una stringa UTF-8 senza problemi, ma ad esempio il lengthmetodo restituisce il numero di byte nella stringa e non il numero di punti di codice.
Qualche programmatore, amico,

8
Ad essere onesti, ottenere la lunghezza in punti di codice di una stringa non ha molti usi. La lunghezza in byte può essere utilizzata per pre-allocare correttamente i buffer, ad esempio.
R. Martinho Fernandes,

2
Il numero di punti di codice in una stringa UTF-8 non è un numero molto interessante: si può scrivere ñcome 'LETTER SMALL LETTER N WITH TILDE' (U + 00F1) (che è un punto di codice) o 'LATIN SMALL LETTER N' ( U + 006E) seguito da "COMBINING TILDE" (U + 0303) che è due punti di codice.
Martin Bonner supporta Monica l'

Tutti quei commenti su "non hai bisogno di questo e non hai bisogno di quel" come "numero di punti di codice non importanti" ecc. Mi suona un po 'sospetto. Una volta che si scrive un parser che dovrebbe analizzare una specie di codice sorgente utf8, spetta alla specifica del parser considerare o meno LATIN SMALL LETTER N' == (U+006E) followed by 'COMBINING TILDE' (U+0303).
BitTickler

4

Tuttavia, esiste una libreria piuttosto utile chiamata tiny-utf8 , che è sostanzialmente una sostituzione drop-in per std::string/ std::wstring. Ha lo scopo di colmare il vuoto della classe contenitore di stringhe utf8 ancora mancante.

Questo potrebbe essere il modo più comodo di "gestire" le stringhe utf8 (cioè senza la normalizzazione unicode e cose simili). Operi comodamente su punti di codice , mentre la stringa rimane codificata in chars con codifica di lunghezza di esecuzione .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.