Cosa c’è di “sbagliato” con C ++ wchar_t e wstrings? Quali sono alcune alternative ai caratteri ampi?

Ho visto molte persone nella comunità C ++ (in particolare ## c ++ su freenode) risentito dell’uso di wstrings e wchar_t , e del loro uso in Windows API. Che cosa è esattamente “sbagliato” con wchar_t e wstring , e se voglio sostenere l’internazionalizzazione, quali sono alcune alternative ai caratteri ampi?

Cos’è wchar_t?

wchar_t è definito in modo tale che la codifica del char di qualsiasi locale possa essere convertita in una rappresentazione wchar_t dove ogni wchar_t rappresenta esattamente un punto di codice:

Tipo wchar_t è un tipo distinto i cui valori possono rappresentare codici distinti per tutti i membri del set di caratteri esteso più grande specificato tra le lingue supportate (22.3.1).

– C ++ [basic.fundamental] 3.9.1 / 5

Questo non richiede che wchar_t sia abbastanza grande da rappresentare qualsiasi carattere da tutte le localizzazioni simultaneamente. Cioè, la codifica usata per wchar_t può differire tra le impostazioni locali. Il che significa che non puoi necessariamente convertire una stringa in wchar_t usando una localizzazione e poi riconvertirla in char usando un’altra localizzazione. 1

Dato che l’utilizzo di wchar_t come rappresentazione comune tra tutte le localizzazioni sembra essere l’uso principale di wchar_t, in pratica potresti chiederti a cosa servirebbe se non fosse così.

L’intento e lo scopo originari di wchar_t era di semplificare l’elaborazione del testo definendolo in modo tale da richiedere un mapping uno-a-uno dalle code-units di una stringa ai caratteri del testo, consentendo così l’uso degli stessi semplici algoritmi utilizzati con le stringhe ascii per lavorare con altre lingue.

Sfortunatamente la formulazione delle specifiche di wchar_t presuppone una mapping uno-a-uno tra caratteri e codepoint per raggiungere questo objective. Unicode rompe tale ipotesi 2 , quindi non è ansible utilizzare tranquillamente wchar_t anche per algoritmi di testo semplici.

Ciò significa che il software portatile non può utilizzare wchar_t come rappresentazione comune per il testo tra le impostazioni locali o per abilitare l’uso di algoritmi di testo semplici.

A che serve oggi wchar_t?

Non molto, per codice portatile comunque. Se __STDC_ISO_10646__ è definito, i valori di wchar_t rappresentano direttamente codepoints Unicode con gli stessi valori in tutte le versioni locali. Questo rende sicuro fare le conversioni inter-locale menzionate in precedenza. Tuttavia non si può fare affidamento solo su di esso per decidere che è ansible utilizzare wchar_t in questo modo perché, mentre la maggior parte delle piattaforms Unix lo definiscono, Windows non funziona anche se Windows utilizza la stessa locale wchar_t in tutte le versioni locali.

Il motivo per cui Windows non definisce __STDC_ISO_10646__ è perché Windows usa UTF-16 come codifica wchar_t e poiché UTF-16 utilizza coppie surrogate per rappresentare codepoint più grandi di U + FFFF, il che significa che UTF-16 non soddisfa i requisiti per __STDC_ISO_10646__ .

Per il codice specifico della piattaforma, wchar_t potrebbe essere più utile. È essenzialmente richiesto su Windows (ad esempio, alcuni file non possono essere aperti senza l’uso di nomi di file wchar_t), anche se Windows è l’unica piattaforma in cui questo è vero per quanto ne so (quindi forse possiamo pensare a wchar_t come ‘Windows_char_t’).

Con il senno di poi, wchar_t non è chiaramente utile per semplificare la gestione del testo o come memoria per il testo indipendente dalla localizzazione. Il codice portatile non dovrebbe tentare di usarlo per questi scopi. Il codice non portatile può essere utile semplicemente perché alcune API lo richiedono.

alternative

L’alternativa che mi piace è usare le stringhe C codificate UTF-8, anche su piattaforms non particolarmente amichevoli verso UTF-8.

In questo modo si può scrivere codice portatile usando una rappresentazione testuale comune su piattaforms, utilizzare tipi di dati standard per lo scopo previsto, ottenere il supporto della lingua per questi tipi (es. Stringhe letterali, anche se alcuni trucchi sono necessari per farlo funzionare per alcuni compilatori), alcuni supporto per librerie standard, supporto per debugger (potrebbero essere necessari ulteriori trucchi), ecc. Con caratteri ampi è generalmente più difficile o imansible ottenere tutto questo, e si possono ottenere pezzi diversi su piattaforms diverse.

Una cosa che UTF-8 non fornisce è la possibilità di utilizzare semplici algoritmi di testo come è ansible con ASCII. In questo UTF-8 non è peggio di qualsiasi altra codifica Unicode. In realtà può essere considerato migliore perché le rappresentazioni di unità multi-codice in UTF-8 sono più comuni e quindi i bug nel codice che gestiscono tali rappresentazioni di caratteri di larghezza variabile hanno più probabilità di essere notati e risolti rispetto a se si tenta di attenersi a UTF -32 con NFC o NFKC.

Molte piattaforms usano UTF-8 come codifica nativa del char e molti programmi non richiedono alcuna significativa elaborazione del testo, e quindi scrivere un programma internazionalizzato su quelle piattaforms è poco diverso dalla scrittura del codice senza considerare l’internazionalizzazione. La scrittura di codice più ampiamente portatile o la scrittura su altre piattaforms richiede l’inserimento di conversioni ai limiti delle API che utilizzano altre codifiche.

Un’altra alternativa utilizzata da alcuni software è quella di scegliere una rappresentazione multipiattaforma, come gli array corti senza segno che contengono i dati UTF-16, quindi fornire tutto il supporto della libreria e vivere semplicemente con i costi nel supporto linguistico, ecc.

C ++ 11 aggiunge nuovi tipi di caratteri larghi come alternative a wchar_t, char16_t e char32_t con le relative funzioni di lingua / libreria. Questi non sono in realtà garantiti per essere UTF-16 e UTF-32, ma non immagino che un’implementazione importante utilizzerà qualcos’altro. C ++ 11 migliora anche il supporto UTF-8, ad esempio con stringhe di stringa UTF-8, quindi non sarà necessario ingannare VC ++ nella produzione di stringhe codificate in UTF-8 (anche se potrei continuare a farlo piuttosto che usare il prefisso u8 ).

Alternative da evitare

TCHAR: TCHAR è per la migrazione di programmi Windows antichi che presuppongono codifiche legacy da char a wchar_t e che è meglio dimenticare a meno che il tuo programma non sia stato scritto in qualche millennio precedente. Non è portatile ed è intrinsecamente non specifico sulla sua codifica e persino sul suo tipo di dati, rendendolo inutilizzabile con qualsiasi API non basata su TCHAR. Poiché il suo scopo è la migrazione a wchar_t, che abbiamo visto sopra non è una buona idea, non c’è alcun valore nell’uso di TCHAR.


1. I caratteri che sono rappresentabili nelle stringhe wchar_t ma che non sono supportati in nessuna locale non devono essere rappresentati con un singolo valore wchar_t. Ciò significa che wchar_t potrebbe usare una codifica a larghezza variabile per certi caratteri, un’altra chiara violazione dell’intenzione di wchar_t. Sebbene sia discutibile che un personaggio sia rappresentabile da wchar_t è sufficiente dire che le impostazioni locali “supportano” quel carattere, nel qual caso le codifiche a larghezza variabile non sono legali e l’uso di UTF-16 da parte di Window non è conforms.

2. Unicode consente di rappresentare molti caratteri con più punti di codice, creando gli stessi problemi per algoritmi di testo semplici come codifiche a larghezza variabile. Anche se si mantiene rigorosamente una normalizzazione composta, alcuni caratteri richiedono ancora più punti di codice. Vedi: http://www.unicode.org/standard/where/

Non c’è nulla di “sbagliato” con wchar_t. Il problema è che, di nuovo in NT 3.x giorni, Microsoft decise che Unicode era buono (lo era) e che implementava Unicode come caratteri wchar_t a 16 bit. Quindi la maggior parte della letteratura Microsoft della metà degli anni ’90 è praticamente uguale a Unicode == utf16 == wchar_t.

Che, purtroppo, non è affatto il caso. “Caratteri ampi” non sono necessariamente 2 byte, su tutte le piattaforms, in tutte le circostanze.

Questo è uno dei migliori primer su “Unicode” (indipendente da questa domanda, indipendente da C ++) che abbia mai visto: lo consiglio vivamente :

E credo onestamente che il modo migliore per gestire “ASCII a 8 bit” rispetto a “Caratteri wide Win32” vs “wchar_t-in-general” è semplicemente accettare che “Windows è diverso” … e codice di conseguenza.

A PARER MIO…

PS:

Sono totalmente d’accordo con jamesdlin sopra:

Su Windows, non hai davvero scelta. Le sue API interne sono state progettate per UCS-2, che era ragionevole al momento in cui era prima che le codifiche UTF-8 e UTF-16 a lunghezza variabile fossero standardizzate. Ma ora che supportano UTF-16, sono finiti con il peggio di entrambi i mondi.

Lettura obbligatoria:

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

Se si programma in Java o .Net (VB.Net o C #) – è in gran parte un non-problema: entrambi sono Unicode per impostazione predefinita. Se si programma nella “classica” API Win32), la soluzione migliore è probabilmente utilizzare le macro TCHAR e _T () (piuttosto che usare wchar esplicitamente).

Tutti i compilatori Microsoft VS2005 e successivi, credo, sono predefiniti a 16 bit per C / C ++ in ogni caso (parte del motivo per cui utilizzo ancora MSVS 6.0 ogni volta che posso;)).

Un altro buon collegamento (anche se un po ‘datato):