example - wchar_t c++
WChars, codificaciones, estándares y portabilidad (4)
¿Es esta la manera correcta de escribir un núcleo de programa idiomático, portátil, universal y que codifica la codificación utilizando solo C / C ++ estándar puro?
No, y no hay manera de cumplir todas estas propiedades, al menos si desea que su programa se ejecute en Windows. En Windows, debe ignorar los estándares C y C ++ en casi todas partes y trabajar exclusivamente con wchar_t
(no necesariamente internamente, sino en todas las interfaces con el sistema). Por ejemplo, si comienzas con
int main(int argc, char** argv)
ya ha perdido soporte Unicode para argumentos de línea de comando. Tu tienes que escribir
int wmain(int argc, wchar_t** argv)
en su lugar, o use la función GetCommandLineW
, ninguna de las cuales se especifica en el estándar C.
Más específicamente,
- cualquier programa compatible con Unicode en Windows debe ignorar activamente el estándar de C y C ++ para cosas como argumentos de línea de comandos, E / S de archivos y consolas, o manipulación de archivos y directorios. Esto ciertamente no es idiomático . Use las extensiones o envoltorios de Microsoft como Boost.Filesystem o Qt en su lugar.
- La portabilidad es extremadamente difícil de lograr, especialmente para el soporte de Unicode. Realmente debes estar preparado para que todo lo que piensas que sabes sea posiblemente incorrecto. Por ejemplo, debe tener en cuenta que los nombres de archivo que utiliza para abrir archivos pueden ser diferentes de los nombres de archivo que realmente se usan, y que dos nombres de archivo aparentemente diferentes pueden representar el mismo archivo. Después de crear dos archivos a y b , puede terminar con un solo archivo c , o dos archivos dy e , cuyos nombres de archivo son diferentes de los nombres de archivo que pasó al sistema operativo. O necesita una biblioteca de envoltura externa o muchos
#ifdef
s. - En general, codificar la agnosticidad no funciona en la práctica, especialmente si quieres ser portátil. Debes saber que
wchar_t
es una unidad de código UTF-16 en Windows y que a menudo (no siempre) una unidad de código UTF-8 en Linux. La concientización de la codificación a menudo es el objetivo más deseable: asegúrese de saber siempre con qué codificación trabaja, o utilice una biblioteca contenedora que los abstraiga.
Creo que tengo que concluir que es completamente imposible construir una aplicación portátil con capacidad Unicode en C o C ++ a menos que esté dispuesto a usar bibliotecas adicionales y extensiones específicas del sistema, y poner mucho esfuerzo en ello. Desafortunadamente, la mayoría de las aplicaciones ya fallan en tareas comparativamente simples como "escribir caracteres griegos en la consola" o "admitir cualquier nombre de archivo permitido por el sistema de manera correcta", y tales tareas son solo los primeros pasos hacia el verdadero soporte Unicode.
Lo siguiente puede no calificar como una pregunta de SO; si está fuera de límites, siéntase libre de decirme que me vaya. La pregunta aquí es básicamente: "¿Comprendo correctamente el estándar C y esta es la manera correcta de hacer las cosas?"
Me gustaría pedir aclaraciones, confirmaciones y correcciones sobre mi comprensión del manejo de caracteres en C (y por lo tanto C ++ y C ++ 0x). Primero, una observación importante:
Portabilidad y serialización son conceptos ortogonales.
Las cosas portátiles son cosas como C, unsigned int
, wchar_t
. Las cosas serializables son cosas como uint32_t
o UTF-8. "Portátil" significa que puede recompilar la misma fuente y obtener un resultado de trabajo en cada plataforma compatible, pero la representación binaria puede ser totalmente diferente (o incluso no existir, por ejemplo TCP-over-carrier paloma). Por otro lado, las cosas serializables siempre tienen la misma representación, por ejemplo, el archivo PNG que puedo leer en mi escritorio de Windows, en mi teléfono o en mi cepillo de dientes. Las cosas portátiles son cosas internas y serializables que tienen que ver con E / S. Las cosas portátiles son tipo seguras, las cosas serializables necesitan un tipo de juego de palabras. </ preámbulo>
Cuando se trata del manejo de caracteres en C, hay dos grupos de cosas relacionadas, respectivamente, con la portabilidad y la serialización:
wchar_t
,setlocale()
,mbsrtowcs()
/wcsrtombs()
: el estándar C no dice nada acerca de "codificaciones" ; de hecho, es completamente agnóstico a cualquier texto o propiedades de codificación. Solo dice "tu punto de entrada esmain(int, char**)
; obtienes un tipowchar_t
que puede contener todos los caracteres de tu sistema; obtienes funciones para leer las secuencias de entrada y convertirlas en wstrings factibles y viceversa.iconv()
y UTF-8,16,32: Una función / biblioteca para transcodificar entre codificaciones definidas, definidas y fijas bien definidas. Todas las codificaciones manejadas por iconv son universalmente entendidas y aceptadas, con una excepción.
El puente entre el mundo portátil, que codifica la agnóstico de C con su tipo de carácter portátil wchar_t
y el mundo determinista del exterior, es la conversión de iconv entre WCHAR-T y UTF .
Entonces, ¿debería siempre almacenar mis cadenas internamente en una interfaz wstring de codificación independiente, con el CRT a través de wcsrtombs()
, y usar iconv()
para la serialización? Conceptualmente:
my program
<-- wcstombs --- /==============/ --- iconv(UTF8, WCHAR_T) -->
CRT | wchar_t[] | <Disk>
--- mbstowcs --> /==============/ <-- iconv(WCHAR_T, UTF8) ---
|
+-- iconv(WCHAR_T, UCS-4) --+
|
... <--- (adv. Unicode malarkey) ----- libicu ---+
Prácticamente, eso significa que escribiría dos envoltorios de placa de caldera para el punto de entrada de mi programa, por ejemplo, para C ++:
// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>
std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
int wmain(const std::vector<std::wstring> args); // user starts here
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
setlocale(LC_CTYPE, "");
int argc;
wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
setlocale(LC_CTYPE, "");
return wmain(parse(argc, argv));
}
#endif
// Serialization utilities
#include <iconv.h>
typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;
U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);
/* ... */
¿Es esta la manera correcta de escribir un núcleo de programa idiomático, portátil, universal y que codifica la codificación usando solo C / C ++ puro y estándar, junto con una interfaz de E / S bien definida para UTF usando iconv? (Tenga en cuenta que problemas como la normalización Unicode o el reemplazo diacrítico están fuera del alcance, solo después de que decida que realmente desea Unicode (a diferencia de cualquier otro sistema de codificación que desee) es hora de tratar con esos detalles, por ejemplo, usando una biblioteca dedicada como libicu.)
Actualizaciones
Después de muchos comentarios muy agradables, me gustaría agregar algunas observaciones:
Si su aplicación explícitamente desea tratar con texto Unicode, debe hacer que
iconv
-conversion parte del núcleo y useuint32_t
/char32_t
-strings internamente con UCS-4.Windows: Si bien usar cadenas anchas generalmente es bueno, parece que la interacción con la consola (cualquier consola, para el caso) es limitada, ya que no parece ser compatible con ninguna codificación de consola de
mbstowcs
ymbstowcs
es esencialmente inútil ( excepto para el ensanchamiento trivial). La recepción de argumentos de cadena ancha de, por ejemplo, un drop de Explorer junto conGetCommandLineW
+CommandLineToArgvW
funciona (tal vez debería haber un contenedor separado para Windows).Sistemas de archivos: los sistemas de archivos no parecen tener ninguna noción de codificación y simplemente toman cualquier cadena terminada en nulo como un nombre de archivo. La mayoría de los sistemas toman cadenas de bytes, pero Windows / NTFS toma cadenas de 16 bits.
char16_t
tener cuidado al descubrir qué archivos existen y al manejar esos datos (por ejemplo,char16_t
secuenciaschar16_t
que no constituyen un UTF16 válido (por ejemplo, sustitutos desnudos) son nombres de archivo NTFS válidos). El estándar Cfopen
no puede abrir todos los archivos NTFS, ya que no existe una conversión posible que se correlacione con todas las cadenas posibles de 16 bits._wfopen
posible que se requiera el uso de_wfopen
específico de Windows. Como corolario, en general, no existe una noción bien definida de "cuántos caracteres" componen un nombre de archivo dado, ya que no hay ninguna noción de "personaje" en primer lugar. Caveat Emptor.
Dado que iconv
no es "C / C ++ estándar puro", no creo que esté satisfaciendo sus propias especificaciones.
Hay nuevas facetas de codecvt
vienen con char32_t
y char16_t
así que no veo cómo puedes estar equivocado, siempre y cuando seas consistente y elijas un tipo de caracteres + codificación si las facetas están aquí.
Las facetas se describen en 22.5 [locale.stdcvt] (desde n3242).
No entiendo cómo esto no satisface al menos algunos de sus requisitos:
namespace ns {
typedef char32_t char_t;
using std::u32string;
// or use user-defined literal
#define LIT u32
// Communicate with interface0, which wants utf-8
// This type doesn''t need to be public at all; I just refactored it.
typedef std::wstring_convert<std::codecvt_utf8<char_T>, char_T> converter0;
inline std::string
to_interface0(string const& s)
{
return converter0().to_bytes(s);
}
inline string
from_interface0(std::string const& s)
{
return converter0().from_bytes(s);
}
// Communitate with interface1, which wants utf-16
// Doesn''t have to be public either
typedef std::wstring_convert<std::codecvt_utf16<char_T>, char_T> converter1;
inline std::wstring
to_interface0(string const& s)
{
return converter1().to_bytes(s);
}
inline string
from_interface0(std::wstring const& s)
{
return converter1().from_bytes(s);
}
} // ns
Entonces su código puede usar ns::string
, ns::char_t
, LIT''A''
& LIT"Hello, World!"
con abandono imprudente, sin saber cuál es la representación subyacente. Luego use from_interfaceX(some_string)
cuando sea necesario. Tampoco afecta la configuración regional o las secuencias. Los ayudantes pueden ser tan inteligentes como sea necesario, por ejemplo, codecvt_utf8
puede tratar con ''encabezados'', que supongo que es estándar de cosas complicadas como la lista de materiales (ditto codecvt_utf16
).
De hecho, escribí lo anterior para ser lo más breve posible, pero realmente querrías ayudantes como este:
template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
return converter0().from_bytes(std::forward<T>(t)...);
}
que le dan acceso a las 3 sobrecargas para cada [from|to]_bytes
, aceptando cosas como, por ejemplo, const char*
o rangos.
El problema con wchar_t
es que el procesamiento de texto wchar_t
la codificación es demasiado difícil y debe evitarse. Si te quedas con "C pura" como dices, puedes usar todas las funciones w*
como wcscat
y amigos, pero si quieres hacer algo más sofisticado, entonces tienes que sumergirte en el abismo.
Aquí hay algunas cosas que son mucho más difíciles con wchar_t
de lo que son si solo elige una de las codificaciones UTF:
Análisis de Javascript: los identificadores pueden contener ciertos caracteres fuera del BMP (y supongamos que te importa este tipo de corrección).
HTML: ¿Cómo se enciende
𐀀
en una cadena dewchar_t
?Editor de texto: ¿Cómo se encuentran los límites del clúster grafema en una cadena
wchar_t
?
Si conozco la codificación de una cadena, puedo examinar los caracteres directamente. Si no conozco la codificación, espero que lo que quiera hacer con una cadena sea implementado por una función de biblioteca en alguna parte. Entonces la portabilidad de wchar_t
es algo irrelevante ya que no lo considero un tipo de datos especialmente útil .
Los requisitos de su programa pueden diferir y puede funcionar bien para usted.
wchar_t
tipo wchar_t
porque depende de la plataforma (no es "serializable" según su definición): UTF-16 en Windows y UTF-32 en la mayoría de los sistemas tipo Unix. En su lugar, use los char16_t
y / o char32_t
de C ++ 0x / C1x. (Si no tiene un compilador nuevo, uint16_t
como uint16_t
y uint32_t
por ahora).
DO define funciones para convertir entre funciones UTF-8, UTF-16 y UTF-32.
NO escriba versiones angostas / anchas sobrecargadas de cada función de cadena como lo hizo la API de Windows con -A y -W. Elija una codificación preferida para usar internamente y cúmplala. Para cosas que necesitan una codificación diferente, conviértalo según sea necesario.