Cosa c'è di "sbagliato" in C ++ wchar_t e wstrings? Quali sono alcune alternative ai caratteri larghi?


87

Ho visto molte persone nella comunità C ++ (in particolare ## c ++ su freenode) risentirsi dell'uso di wstringse wchar_t, e del loro uso nell'API di Windows. Cosa c'è di esattamente "sbagliato" in wchar_te wstring, e se voglio sostenere l'internazionalizzazione, quali sono alcune alternative ai caratteri larghi?


1
Hai dei riferimenti per questo?
Dani

14
Forse questo fantastico thread risponderà a tutte le tue domande? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox

15
Su Windows, non hai davvero scelta. Le sue API interne erano progettate per UCS-2, il che era ragionevole all'epoca poiché 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.
jamesdlin

12
utf8everywhere.org ha una buona discussione sui motivi per evitare caratteri larghi.
JoeG

5
@jamesdlin Certamente hai una scelta. la libreria nowide fornisce un modo conveniente per convertire le stringhe solo quando si passano alle API. Le chiamate API con stringhe sono generalmente a bassa frequenza, quindi il modo ragionevole è convertire ad-hok e avere file e variabili interne in UTF-8 tutto il tempo.
Pavel Radzivilovsky,

Risposte:


115

Cos'è wchar_t?

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

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

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

Ciò non richiede che wchar_t sia abbastanza grande da rappresentare simultaneamente qualsiasi carattere da tutte le impostazioni locali. Cioè, la codifica utilizzata per wchar_t può differire tra le lingue. Ciò significa che non è possibile convertire necessariamente una stringa in wchar_t utilizzando una locale e quindi riconvertirla in char utilizzando un'altra locale. 1

Poiché l'utilizzo di wchar_t come rappresentazione comune tra tutte le impostazioni locali sembra essere l'uso principale di wchar_t in pratica, potresti chiederti a cosa serve se non quello.

L'intento originale e lo scopo di wchar_t era quello di rendere semplice l'elaborazione del testo definendolo in modo tale da richiedere una mappatura uno-a-uno dalle unità di codice di una stringa ai caratteri del testo, consentendo così l'uso degli stessi semplici algoritmi utilizzati con stringhe ascii per lavorare con altri linguaggi.

Sfortunatamente la formulazione delle specifiche di wchar_t presuppone una mappatura uno-a-uno tra caratteri e punti di codice per ottenere ciò. Unicode infrange questa ipotesi 2 , quindi non puoi usare tranquillamente wchar_t per semplici algoritmi di testo.

Ciò significa che il software portatile non può utilizzare wchar_t né come rappresentazione comune del testo tra le impostazioni locali, né per abilitare l'uso di semplici algoritmi di testo.

A cosa serve wchar_t oggi?

Non molto, comunque per il codice portatile. Se __STDC_ISO_10646__è definito, i valori di wchar_t rappresentano direttamente i punti di codice Unicode con gli stessi valori in tutte le impostazioni locali. Ciò rende sicuro eseguire le conversioni inter-locale menzionate in precedenza. Tuttavia non puoi fare affidamento solo su di esso per decidere che puoi usare wchar_t in questo modo perché, mentre la maggior parte delle piattaforme unix lo definisce, Windows non lo fa 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 utilizza UTF-16 come codifica wchar_t e poiché UTF-16 utilizza coppie surrogate per rappresentare punti di codice maggiori 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 semplicemente non possono essere aperti senza utilizzare i nomi di file wchar_t), sebbene Windows sia l'unica piattaforma in cui questo è vero per quanto ne so (quindi forse possiamo pensare a wchar_t come 'Windows_char_t').

Col senno di poi wchar_t non è chiaramente utile per semplificare la gestione del testo o come archivio per il testo indipendente dalla locale. Il codice portatile non dovrebbe tentare di usarlo per questi scopi. Il codice non portabile potrebbe trovarlo utile semplicemente perché alcune API lo richiedono.

Alternative

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

In questo modo è possibile scrivere codice portatile utilizzando una rappresentazione di testo comune su piattaforme, utilizzare tipi di dati standard per lo scopo previsto, ottenere il supporto del linguaggio per quei tipi (ad esempio stringhe letterali, sebbene alcuni trucchi siano necessari per farlo funzionare con alcuni compilatori), alcuni supporto delle librerie standard, supporto del debugger (potrebbero essere necessari più trucchi), ecc. Con caratteri larghi è generalmente più difficile o impossibile ottenere tutto questo e potresti ottenere pezzi diversi su piattaforme diverse.

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

Molte piattaforme utilizzano UTF-8 come codifica char nativa e molti programmi non richiedono alcuna elaborazione significativa del testo, quindi scrivere un programma internazionalizzato su quelle piattaforme è leggermente diverso dalla scrittura di codice senza considerare l'internazionalizzazione. Scrivere codice più ampiamente portabile o scrivere su altre piattaforme richiede l'inserimento di conversioni ai confini delle API che utilizzano altre codifiche.

Un'altra alternativa utilizzata da alcuni software è quella di scegliere una rappresentazione multipiattaforma, come array brevi non firmati che contengono dati UTF-16, e quindi fornire tutto il supporto della libreria e semplicemente convivere con i costi del supporto linguistico, ecc.

C ++ 11 aggiunge nuovi tipi di caratteri larghi come alternative a wchar_t, char16_t e char32_t con funzioni di linguaggio / libreria associate. In realtà non è garantito che siano UTF-16 e UTF-32, ma non immagino che nessuna implementazione principale utilizzerà nient'altro. C ++ 11 migliora anche il supporto UTF-8, ad esempio con i valori letterali di stringa UTF-8, quindi non sarà necessario ingannare VC ++ nella produzione di stringhe codificate UTF-8 (anche se posso continuare a farlo piuttosto che usare il u8prefisso) .

Alternative da evitare

TCHAR: TCHAR è per la migrazione di vecchi programmi Windows che assumono codifiche legacy da char a wchar_t, ed è meglio dimenticarlo a meno che il tuo programma non sia stato scritto in qualche millennio precedente. Non è portabile ed è intrinsecamente non specifico riguardo alla sua codifica e persino al 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'usare 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 utilizzare una codifica a larghezza variabile per determinati caratteri, un'altra chiara violazione dell'intento di wchar_t. Sebbene sia discutibile che un carattere rappresentabile da wchar_t sia sufficiente per dire che la locale "supporta" quel carattere, nel qual caso le codifiche a larghezza variabile non sono legali e l'uso di UTF-16 da parte di Window non è conforme.

2. Unicode consente di rappresentare molti caratteri con più punti di codice, il che crea gli stessi problemi per semplici algoritmi di testo delle 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/


3
Aggiunta: utf8everywhere.org consiglia di utilizzare UTF-8 su Windows e Boost.Nowide è pianificato per una revisione formale.
Yakov Galka

2
La cosa migliore, ovviamente, è usare C # o VB.Net su Windows :) O semplicemente il vecchio C / Win32. Ma se devi usare C ++, allora TCHAR è il modo migliore per farlo. Il valore predefinito è "wchar_t" su MSVS2005 e versioni successive. IMHO ...
paulsm4

4
@BrendanMcK: certo, il codice che utilizza l'API Win32 su Windows e altre API su altri sistemi non esiste. Destra? Il problema con l'approccio di microsoft ("usa wchar internamente ovunque nella tua app") è che interessa anche il codice che non interfaccia direttamente il sistema e potrebbe essere portabile.
Yakov Galka

4
Il problema è che si deve utilizzare le funzioni specifiche di Windows, perché la decisione di Microsoft di non supporto UTF-8 come una tabella codici ANSI "rompe" la standard C (++) biblioteca. Ad esempio, non è possibile fopenun file il cui nome contiene caratteri non ANSI.
dan04

11
@ dan04 Sì, non è possibile utilizzare la libreria standard su Windows, ma è possibile creare un'interfaccia portatile che avvolge la libreria standard su altre piattaforme e converte direttamente da UTF-8 a wchar_t prima di utilizzare le funzioni W di Win32.
bames53

20

Non c'è niente di "sbagliato" con wchar_t. Il problema è che, ai tempi di NT 3.x, Microsoft ha deciso che Unicode era buono (lo è) e di implementare Unicode come caratteri wchar_t a 16 bit. Quindi la maggior parte della letteratura Microsoft della metà degli anni '90 ha praticamente identificato Unicode == utf16 == wchar_t.

Il che, purtroppo, non è affatto così. I "caratteri larghi" non sono necessariamente 2 byte, su tutte le piattaforme, 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 onestamente credo che il modo migliore per gestire "ASCII a 8 bit" vs "caratteri larghi Win32" vs "wchar_t-in-general" sia semplicemente accettare che "Windows è diverso" ... e codificare di conseguenza.

A PARER MIO...

PS:

Sono totalmente d'accordo con jamesdlin sopra:

Su Windows, non hai davvero scelta. Le sue API interne erano progettate per UCS-2, il che era ragionevole all'epoca poiché 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.

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.