¿Qué está "mal" con C ++ wchar_t y wstrings? ¿Cuáles son algunas alternativas a los caracteres anchos?

86

He visto a mucha gente en la comunidad de C ++ (particularmente ## c ++ en freenode) resentir el uso de wstringsy wchar_t, y su uso en la API de Windows. ¿Qué es exactamente "malo" con wchar_ty wstring, y si quiero apoyar la internacionalización, cuáles son algunas alternativas a los caracteres anchos?

Ken Li
fuente
1
¿Tiene alguna referencia para eso?
Dani
14
¿Quizás este increíble hilo responda a todas tus preguntas? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox
15
En Windows, realmente no tiene otra opción. Sus API internas fueron diseñadas para UCS-2, lo cual era razonable en ese momento ya que era antes de que se estandarizaran las codificaciones UTF-8 y UTF-16 de longitud variable. Pero ahora que admiten UTF-16, terminaron con lo peor de ambos mundos.
jamesdlin
12
utf8everywhere.org tiene una buena discusión sobre las razones para evitar los caracteres amplios.
JoeG
5
@jamesdlin Ciertamente, tienes una opción. La biblioteca nowide proporciona una forma conveniente de convertir cadenas justo cuando se pasan a las API. Las llamadas a la API con cadenas suelen ser de baja frecuencia, por lo que la forma razonable es convertir ad-hok y tener archivos y variables internas en UTF-8 todo el tiempo.
Pavel Radzivilovsky

Respuestas:

114

¿Qué es wchar_t?

wchar_t se define de modo que la codificación char de cualquier configuración regional se pueda convertir en una representación wchar_t donde cada wchar_t representa exactamente un punto de código:

El tipo wchar_t es un tipo diferenciado cuyos valores pueden representar códigos distintos para todos los miembros del conjunto de caracteres extendido más grande especificado entre las configuraciones regionales admitidas (22.3.1).

                                                                               - C ++ [básico.fundamental] 3.9.1 / 5

Esto no requiere que wchar_t sea lo suficientemente grande para representar cualquier carácter de todas las configuraciones regionales simultáneamente. Es decir, la codificación utilizada para wchar_t puede diferir entre las configuraciones regionales. Lo que significa que no es necesario convertir una cadena en wchar_t usando una configuración regional y luego volver a convertir a char usando otra configuración regional. 1

Dado que usar wchar_t como una representación común entre todas las configuraciones regionales parece ser el uso principal de wchar_t en la práctica, es posible que se pregunte para qué sirve si no es para eso.

La intención y el propósito originales de wchar_t era simplificar el procesamiento de texto definiéndolo de modo que requiera una asignación uno a uno de las unidades de código de una cadena a los caracteres del texto, lo que permite el uso de los mismos algoritmos simples que se utilizan con cadenas ascii para trabajar con otros lenguajes.

Desafortunadamente, la redacción de la especificación de wchar_t asume un mapeo uno a uno entre caracteres y puntos de código para lograr esto. Unicode rompe esa suposición 2 , por lo que tampoco puede usar wchar_t de manera segura para algoritmos de texto simples.

Esto significa que el software portátil no puede usar wchar_t como una representación común de texto entre configuraciones regionales o para permitir el uso de algoritmos de texto simples.

¿De qué sirve wchar_t hoy?

No mucho, para el código portátil de todos modos. Si __STDC_ISO_10646__se define, los valores de wchar_t representan directamente los puntos de código Unicode con los mismos valores en todas las configuraciones regionales. Eso hace que sea seguro realizar las conversiones entre ubicaciones mencionadas anteriormente. Sin embargo, no puede confiar solo en él para decidir que puede usar wchar_t de esta manera porque, aunque la mayoría de las plataformas Unix lo definen, Windows no lo hace, aunque Windows usa la misma configuración regional wchar_t en todas las configuraciones regionales.

La razón por la que Windows no define __STDC_ISO_10646__es porque Windows usa UTF-16 como su codificación wchar_t, y porque UTF-16 usa pares sustitutos para representar puntos de código mayores que U + FFFF, lo que significa que UTF-16 no satisface los requisitos de __STDC_ISO_10646__.

Para el código específico de la plataforma, wchar_t puede ser más útil. Esencialmente se requiere en Windows (por ejemplo, algunos archivos simplemente no se pueden abrir sin usar los nombres de archivo wchar_t), aunque Windows es la única plataforma donde esto es cierto hasta donde yo sé (así que tal vez podamos pensar en wchar_t como 'Windows_char_t').

En retrospectiva, wchar_t claramente no es útil para simplificar el manejo de texto o como almacenamiento de texto independiente de la configuración regional. El código portátil no debe intentar utilizarlo para estos fines. El código no portátil puede resultar útil simplemente porque alguna API lo requiere.

Alternativas

La alternativa que me gusta es usar cadenas C codificadas en UTF-8, incluso en plataformas que no son particularmente amigables con UTF-8.

De esta manera, uno puede escribir código portátil usando una representación de texto común en todas las plataformas, usar tipos de datos estándar para su propósito previsto, obtener el soporte del lenguaje para esos tipos (por ejemplo, cadenas literales, aunque algunos trucos son necesarios para que funcione para algunos compiladores), algunos soporte de biblioteca estándar, soporte de depurador (pueden ser necesarios más trucos), etc. Con caracteres anchos, generalmente es más difícil o imposible obtener todo esto, y puede obtener diferentes piezas en diferentes plataformas.

Una cosa que UTF-8 no proporciona es la capacidad de usar algoritmos de texto simples como los que son posibles con ASCII. En este UTF-8 no es peor que cualquier otra codificación Unicode. De hecho, se puede considerar que es mejor porque las representaciones de unidades de código múltiple en UTF-8 son más comunes y, por lo tanto, es más probable que se noten y corrijan los errores en el manejo del código, tales representaciones de caracteres de ancho variable que si intenta ceñirse a UTF -32 con NFC o NFKC.

Muchas plataformas usan UTF-8 como su codificación nativa de caracteres y muchos programas no requieren ningún procesamiento de texto significativo, por lo que escribir un programa internacionalizado en esas plataformas es un poco diferente de escribir código sin considerar la internacionalización. Escribir código más ampliamente portátil o escribir en otras plataformas requiere insertar conversiones en los límites de las API que usan otras codificaciones.

Otra alternativa utilizada por algunos software es elegir una representación multiplataforma, como arreglos cortos sin firmar que contienen datos UTF-16, y luego proporcionar todo el soporte de la biblioteca y simplemente vivir con los costos de soporte de idiomas, etc.

C ++ 11 agrega nuevos tipos de caracteres anchos como alternativas a wchar_t, char16_t y char32_t con funciones de idioma / biblioteca asociadas. En realidad, no se garantiza que sean UTF-16 y UTF-32, pero no creo que ninguna implementación importante utilice otra cosa. C ++ 11 también mejora la compatibilidad con UTF-8, por ejemplo, con literales de cadena UTF-8, por lo que no será necesario engañar a VC ++ para que produzca cadenas codificadas en UTF-8 (aunque puedo continuar haciéndolo en lugar de usar el u8prefijo) .

Alternativas a evitar

TCHAR: TCHAR es para migrar programas antiguos de Windows que asumen codificaciones heredadas de char a wchar_t, y es mejor olvidarlo a menos que su programa haya sido escrito en un milenio anterior. No es portátil y es inherentemente inespecífico acerca de su codificación e incluso su tipo de datos, lo que lo hace inutilizable con cualquier API que no sea TCHAR. Dado que su propósito es la migración a wchar_t, que hemos visto anteriormente no es una buena idea, no tiene ningún valor usar TCHAR.


1. No es necesario que los caracteres que se pueden representar en cadenas wchar_t pero que no se admiten en ninguna configuración regional se representen con un solo valor wchar_t. Esto significa que wchar_t podría usar una codificación de ancho variable para ciertos caracteres, otra clara violación de la intención de wchar_t. Aunque se puede argumentar que un carácter representable por wchar_t es suficiente para decir que la configuración regional 'admite' ese carácter, en cuyo caso las codificaciones de ancho variable no son legales y el uso de UTF-16 de Windows no es conforme.

2. Unicode permite representar muchos caracteres con múltiples puntos de código, lo que crea los mismos problemas para los algoritmos de texto simples que las codificaciones de ancho variable. Incluso si uno mantiene estrictamente una normalización compuesta, algunos caracteres aún requieren múltiples puntos de código. Ver: http://www.unicode.org/standard/where/

bames53
fuente
3
Además: utf8everywhere.org recomienda usar UTF-8 en Windows, y Boost.Nowide está programado para una revisión formal.
Yakov Galka
2
Lo mejor, por supuesto, es usar C # o VB.Net en Windows :) O el viejo C / Win32. Pero si debe usar C ++, entonces TCHAR es la mejor manera de hacerlo. Que por defecto es "wchar_t" en MSVS2005 y superior. En mi humilde opinión ...
paulsm4
4
@BrendanMcK: Claro, el código que usa la API Win32 en Windows y otras API en otros sistemas no existe. ¿Derecho? El problema con el enfoque de Microsoft ("use wchar internamente en todas partes de su aplicación") es que afecta incluso al código que no interactúa directamente con el sistema y podría ser portátil.
Yakov Galka
4
El problema es que tiene que usar funciones específicas de Windows porque la decisión de Microsoft de no admitir UTF-8 como una página de códigos ANSI "rompe" la biblioteca estándar C (++). Por ejemplo, no puede fopenun archivo cuyo nombre contenga caracteres que no sean ANSI.
dan04
11
@ dan04 Sí, no puede usar la biblioteca estándar en Windows, pero puede crear una interfaz portátil que envuelva la biblioteca estándar en otras plataformas y la convierta de UTF-8 a wchar_t directamente antes de usar las funciones de Win32 W.
bames53
20

No hay nada "malo" con wchar_t. El problema es que, en los días de NT 3.x, Microsoft decidió que Unicode era bueno (lo es) e implementar Unicode como caracteres wchar_t de 16 bits. Entonces, la mayoría de la literatura de Microsoft de mediados de los 90 prácticamente equiparaba Unicode == utf16 == wchar_t.

Lo cual, lamentablemente, no es en absoluto el caso. Los "caracteres anchos" no son necesariamente de 2 bytes, en todas las plataformas, en todas las circunstancias.

Este es uno de los mejores cebadores en "Unicode" (independiente de esta pregunta, independiente de C ++) que he visto: lo recomiendo encarecidamente:

Y honestamente creo que la mejor manera de lidiar con "ASCII de 8 bits" vs "caracteres anchos de Win32" vs "wchar_t-in-general" es simplemente aceptar que "Windows es diferente" ... y codificar en consecuencia.

EN MI HUMILDE OPINIÓN...

PD:

Estoy totalmente de acuerdo con jamesdlin arriba:

En Windows, realmente no tiene otra opción. Sus API internas fueron diseñadas para UCS-2, lo cual era razonable en ese momento, ya que era antes de que se estandarizaran las codificaciones UTF-8 y UTF-16 de longitud variable. Pero ahora que admiten UTF-16, terminaron con lo peor de ambos mundos.

paulsm4
fuente