c++ c++11 locale

c++ - ¿Cómo puedo usar std:: imbue para establecer la configuración regional para std:: wcout?



c++11 locale (3)

En esta respuesta, tomo las preguntas en orden inverso y agrego otra (con respuesta) que surgió en el camino.

¿Hay alguna forma de usar imbue lugar de establecer la configuración regional global para hacer lo que quiero?

Sí. De forma predeterminada, std::wcout está sincronizado con la secuencia C de stdout subyacente. Por lo tanto, std::wcout puede usar std::wcout si esa sincronización está desactivada, lo que permite que la secuencia de C ++ funcione de manera independiente. Entonces, para modificar el código original para usar imbue y trabajar según lo previsto, solo se debe agregar una sola línea, llamando a std::ios_base::sync_with_stdio :

std::ios_base::sync_with_stdio(false); std::wcout.imbue(ru);

¿Por qué no funcionó la versión original?

El estándar (me refiero a INCITS / ISO / IEC 14882-2011 [2012]) dice muy poco sobre el vínculo con el flujo de stdio subyacente, pero en 27.4.3 dice

El objeto wcout controla la salida a un búfer de flujo asociado con el objeto stdout , declarado en <cstdio>

Además, sin establecer explícitamente una configuración regional global, la configuración regional es la configuración regional "C" que es ASCII en inglés de EE. UU., Por lo que esto parece implicar que stdout tendrá, de forma predeterminada, una asignación ASCII. Dado que no hay caracteres cirílicos representados en ASCII, ¿qué es lo que convierte al ruso adecuado en una serie de caracteres ? caracteres.

¿Por qué la llamada sync_with_stdio debe preceder a sync_with_stdio ?

Según el 27.5.3.4 de la norma:

Si se ha producido alguna operación de entrada o salida utilizando las secuencias estándar antes de la llamada, el efecto se define por la implementación. De lo contrario, llamado con un argumento falso, permite que los flujos estándar funcionen independientemente de los flujos C estándar.

Estoy tratando de usar el mecanismo std::locale en C ++ 11 para contar palabras en diferentes idiomas. Específicamente, tengo std::wstringstream que contiene el título de una famosa novela rusa ("Crime and Punishment" en inglés). Lo que quiero hacer es usar la configuración regional adecuada ( ru_RU.utf8 en mi máquina con Linux) para leer la cadena de caracteres, contar las palabras e imprimir los resultados. Probablemente también debería tener en cuenta que mi sistema está configurado para usar la en_US.utf8 regional en_US.utf8 .

El resultado deseado es este:

0: "Преступление" 1: "и" 2: "наказание" I counted 3 words. and the last word was "наказание"

Todo esto funciona cuando configuro la configuración regional global, pero no cuando intento imbue la secuencia wcout . Cuando intento eso, obtengo este resultado en su lugar:

0: "????????????" 1: "?" 2: "?????????" I counted 3 words. and the last word was "?????????"

Además, cuando intento usar una solución sugerida en los comentarios, (que se puede activar cambiando #define USE_CODECVT 0 a #define USE_CODECVT 1 ), #define USE_CODECVT 1 el error mencionado en esta otra pregunta .

Aquellos interesados ​​en experimentar con el código, o con la configuración del compilador o ambos pueden desear usar este código en vivo .

Mis preguntas

  1. ¿Por qué eso no funciona? ¿Es porque wcout ya está abierto?
  2. ¿Hay alguna forma de usar imbue lugar de establecer la configuración regional global para hacer lo que quiero?

Si hace una diferencia, estoy usando g ++ 4.8.3. El código completo se muestra a continuación.

getwords.cpp

#include <iostream> #include <fstream> #include <sstream> #include <string> #include <locale> #define USE_CODECVT 0 #define USE_IMBUE 1 #if USE_CODECVT #include <codecvt> #endif using namespace std; int main() { #if USE_CODECVT locale ru("ru_RU.utf8", new codecvt_utf8<wchar_t, 0x10ffff, consume_header>{}); #else locale ru("ru_RU.utf8"); #endif #if USE_IMBUE wcout.imbue(ru); #else locale::global(ru); #endif wstringstream in{L"Преступление и наказание"}; in.imbue(ru); wstring word; unsigned wordcount = 0; while (in >> word) { wcout << wordcount << ": /"" << word << "/"/n"; ++wordcount; } wcout << "/nI counted " << wordcount << " words./n" << "and the last word was /"" << word << "/"/n"; }


No sé qué idiomas está planeando admitir, pero hay idiomas en los que su algoritmo no se aplica, por ejemplo. Japonés. Sugiero revisar la palabra iteradores en Componentes internacionales para Unicode. http://userguide.icu-project.org/boundaryanalysis


Primero hice una prueba más con su código y puedo confirmar que L"Преступление и наказание" es una cadena UTF16 correcta. Yo 0x41f, 0x440, 0x435, 0x441, 0x442, 0x443, 0x43f, 0x43b, 0x435, 0x43d, 0x438, 0x435, 0x20, 0x438, 0x20, 0x43d, 0x430, 0x43a, 0x430, 0x437, 0x430, 0x43d, 0x438, 0x435 el código de los caracteres individuales, y son correctamente 0x41f, 0x440, 0x435, 0x441, 0x442, 0x443, 0x43f, 0x43b, 0x435, 0x43d, 0x438, 0x435, 0x20, 0x438, 0x20, 0x43d, 0x430, 0x43a, 0x430, 0x437, 0x430, 0x43d, 0x438, 0x435

No pude encontrar ninguna referencia al respecto, pero parece que simplemente llamar a imbue no es suficiente. basic_ios un método de basic_ios que es un antepasado de cout y wcout . Actúa sobre las conversiones numéricas, pero en todas mis pruebas, no tiene ningún efecto en el conjunto de caracteres utilizado para la salida.

De forma predeterminada, la configuración regional utilizada en un programa C ++ (o C) es ... la configuración regional C que no sabe nada acerca de Unicode. Todos los caracteres ASCII imprimibles (por debajo de 128) se emiten tal como están, y los demás se reemplazan con un ? . Es exactamente lo que hace tu programa.

Para que funcione correctamente, debe seleccionar una configuración regional que sepa sobre caracteres Unicode con setlocale . Una vez hecho esto, puede cambiar la conversión numérica llamando a imbue , y al seleccionar un juego de caracteres Unicode, todo estará bien.

Por lo tanto, siempre que su ubicación actual use un conjunto de caracteres UTF-8, solo tiene que agregar

setlocale(LC_ALL, "");

como primera línea en su programa, y ​​la salida será la esperada:

0: "Преступление" 1: "и" 2: "наказание" I counted 3 words. and the last word was "наказание"

Si su configuración regional actual no utiliza UTF-8, elija una que esté instalada en su sistema y que la admita. Utilicé setlocale(LC_ALL, "fr_FR.UTF-8"); , o incluso setlocale(LC_ALL, "en_US.UTF-8"); y ambos trabajaron.

Editar:

De hecho, la mejor forma de generar correctamente unicode en la pantalla es usar setlocale(LC_ALL, ""); . Se adapta automáticamente al conjunto de caracteres actual. Probé con una variante reducida usando el conjunto de caracteres Latin1 (mi sistema habla francés nativo y no ruso ...)

#include <iostream> #include <locale> using namespace std; int main() { setlocale(LC_ALL, ""); wchar_t ws[] = { 0xe8, 0xe9, 0 }; wcout << ws << endl; }

Lo probé en Linux usando el conjunto de caracteres UTF-8 e ISO-8859-1 (latin1) (resp export LANG=fr_FR.UTF-8 y export LANG=fr_FR.ISO-8859-1 ) y obtuve el èé correctamente en el conjunto de caracteres adecuado . Lo probé también bajo Windows XP, con la página de códigos 851 (OEM) y 1252 (ansi) (resp. chcp 850 y chcp 1252 con el chcp 1252 caracteres de la consola Lucida), y también obtuve el èé en la consola.

Edición 2:

Por supuesto, también puede establecer una configuración locale::global(locale(""); C ++ global con locale::global(locale(""); con la configuración regional predeterminada o locale::global(locale("ru_RU.UTF-8"); con la configuración regional en ruso, pero es más que simplemente llamar a setlocale . De acuerdo con la documentación de la implementación de Gnu de C ++ Standard Library sobre locale : solo hay una relación (del mecanismo de locale C ++) con el mecanismo de locale C: el locale C global se modifica si un objeto locale C ++ nombrado se establece como la configuración regional global ", es decir: std::locale::global(std::locale("")); afecta las funciones de C como si se hubiera realizado la siguiente llamada: std::setlocale(LC_ALL, ""); Por otro lado, no hay viceversa, es decir, llamar a setlocale no tiene ningún tipo de mecanismo de configuración regional de C ++, en particular sobre el funcionamiento de la configuración regional ("") .

Así que realmente parece que había un mecanismo subyacente de la biblioteca C que debería habilitarse primero con setlocale para permitir que la conversión de imbue funcione correctamente.