2009-10-02 8 views
18

Honestamente, simplemente no obtengo la siguiente decisión de diseño en la biblioteca estándar de C++. Al escribir caracteres anchos a un archivo, el wofstream convierte en wchar_tchar caracteres:¿Por qué Wide File-Stream en C++ estrecho escribe datos por defecto?

#include <fstream> 
#include <string> 

int main() 
{ 
    using namespace std; 

    wstring someString = L"Hello StackOverflow!"; 
    wofstream file(L"Test.txt"); 

    file << someString; // the output file will consist of ASCII characters! 
} 

Soy consciente de que esto tiene que ver con el estándar codecvt. Hay codecvt para utf8 en Boost. Además, hay un codecvt para utf16 por Martin York here on SO. La pregunta es ¿por qué el standard codecvt convierte caracteres anchos? ¿por qué no escribir los personajes como son?

Además, ¿vamos a obtener unicode streams real con C++ 0x o me falta algo aquí?

+3

Buena pregunta. Espero que puedas desenterrar una respuesta. Personalmente me inclino por la teoría de "IOStreams es solo una biblioteca mal diseñada" ...;) Probablemente no ayude que Unicode no esté exactamente bien establecido cuando se diseñó la biblioteca. Podrían haber pensado que la serialización hacia/desde caracteres simples era el enfoque más portátil. – jalf

+0

@jalf Gracias. No soy muy competente con las transmisiones pero esta pregunta me molesta mucho: D – AraK

Respuesta

7

El modelo utilizado por C++ para juegos de caracteres se hereda de C, y así se remonta a al menos 1989.

dos puntos principales:

  • IO se realiza en términos de Char.
  • que es el trabajo de la configuración regional para determinar cómo caracteres de ancho son serializados
  • la localidad por defecto (llamado "C") es muy mínimo (que no recuerdo las limitaciones de la norma, aquí es capaz de manejar solo ASCII de 7 bits como conjunto de caracteres angosto y ancho).
  • hay un local determinado entorno llamado ""

Así que para conseguir cualquier cosa, usted tiene que establecer la configuración regional.

Si utilizo el programa simple

#include <locale> 
#include <fstream> 
#include <ostream> 
#include <iostream> 

int main() 
{ 
    wchar_t c = 0x00FF; 
    std::locale::global(std::locale("")); 
    std::wofstream os("test.dat"); 
    os << c << std::endl; 
    if (!os) { 
     std::cout << "Output failed\n"; 
    } 
} 

que utilizan la configuración regional de medio ambiente y de salida el carácter ancho del código 0x00FF en un archivo. Si le pido a utilizar la localización "C", consigo

$ env LC_ALL=C ./a.out 
Output failed 

la configuración regional ha sido incapaz de manejar el gran carácter y que recibirá una notificación del problema ya que el IO falló. Si me quedo pedir una localización UTF-8, consigo

$ env LC_ALL=en_US.utf8 ./a.out 
$ od -t x1 test.dat 
0000000 c3 bf 0a 
0000003 

(-t desde x1 simplemente volcar el archivo representado en hexadecimal), exactamente lo que esperaba para un archivo codificado en UTF-8.

+0

Apuesto a que la salida falló porque esperaba otro personaje. Y el segundo no es lo que esperaría. a menos que ignore por completo los bits altos del wchar_t. ¿Qué sucede si saca c = 0xABCD; ¿Está codificando el CD en UTF-8 e ignorando el AB? o está todo codificado. ¿Qué sucede cuando el carácter UTF-8 tiene tres bytes de longitud? –

+0

También obtengo resultados diferentes. C: (ff 0a) en_US.utf8: (std :: runtime_error [locale :: facet :: _ S_create_c_locale nombre no válido]) –

+0

No entiendo por qué C3 BF no es la codificación de 0x00FF que esperaba. Y para 0xABCD da EA AF 8D, que es lo que esperaba. Lo que no esperaba es que permitiera 0xDCBA (es un punto de código sustituto y no válido) y otros puntos de código no válidos. – AProgrammer

13

una respuesta muy parcial a la primera pregunta: ¿Un archivo es una secuencia de bytes por lo que, cuando se trata de wchar_t 's, al menos algunos de conversión entre wchar_t y char debe ocurrir. Hacer esta conversión de forma "inteligente" requiere el conocimiento de las codificaciones de caracteres, por lo que esta es la razón por la cual se permite que esta conversión dependa de la configuración regional, en virtud del uso de una faceta en la configuración regional de la secuencia.

Luego, la pregunta es cómo se debe hacer esa conversión en la única configuración regional requerida por la norma: la "clásica". No hay una respuesta "correcta" para eso, y el estándar es muy vago al respecto. Entiendo por su pregunta que usted supone que lanzar ciegamente (o memcpy() - ing) entre wchar_t [] y char [] hubiera sido una buena manera. Esto no es irracional, y de hecho es lo que se hace (o al menos se hizo) en algunas implementaciones.

Otro POV sería que, dado que un codecvt es una faceta de configuración regional, es razonable esperar que la conversión se realice utilizando la "codificación de la configuración regional" (aquí estoy manual, ya que el concepto es bastante confuso). Por ejemplo, uno esperaría que un local turco usara ISO-8859-9, o un japonés para usar Shift JIS. Por similitud, la configuración regional "clásica" se convertiría a esta "codificación de la configuración regional". Aparentemente, Microsoft eligió simplemente recortar (lo que lleva a IS-8859-1 si asumimos que wchar_t representa UTF-16 y que nos mantenemos en el plano multilingüe básico), mientras que la implementación de Linux que conozco decidió adherirse a ASCII.

Para su segunda pregunta:

Además, estamos va a conseguir flujos reales Unicode con C++ 0x o me estoy perdiendo algo aquí?

En la sección [locale.codecvt] de n2857 (el último borrador C++ 0x tengo a mano), se puede leer:

La especialización codecvt<char16_t, char, mbstate_t> convierte entre el UTF-16 y Los esquemas de codificación UTF-8 y la especialización codecvt <char32_t, char, mbstate_t> convierten los esquemas de codificación UTF-32 y UTF-8. codecvt<wchar_t,char,mbstate_t> convierte entre los juegos de caracteres nativos para caracteres angostos y anchos.

En la [configuración regional.stdcvt] sección, encontramos:

Para la faceta codecvt_utf8: - La faceta deberá convertir entre UTF-8 secuencias de varios bytes y UCS2 o UCS4 (dependiendo del tamaño de Elem) dentro del programa. [...]

Para la faceta codecvt_utf16: - La faceta deberá convertir entre UTF-16 secuencias de varios bytes y UCS2 o UCS4 (dependiendo del tamaño de Elem) dentro del programa. [...]

Para la faceta codecvt_utf8_utf16: - La faceta deberá convertir entre UTF-8 secuencias de varios bytes y (uno o dos códigos de 16 bits) 16 UTF-dentro del programa.

Así que supongo que esto significa "sí", pero tendría que ser más preciso sobre lo que quiere decir con "transmisiones de Unicode reales" para estar seguro.

+0

@ Éric Gracias. Finalmente estamos obteniendo transmisiones Unicode reales :) – AraK

+0

@ Éric Quise decir que las transmisiones son conscientes de Unicode, como C++ 0x. Todavía estoy buscando una respuesta racional sobre la pregunta principal. – AraK

3

No sé sobre wofstream. Pero C++ 0x incluirá nuevos tipos de caracteres distict (char16_t, char32_t) de ancho garantizado y firmado (sin signo) que pueden utilizarse de forma portátil para UTF-8, UTF-16 y UTF-32. Además, habrá nuevos literales de cadena (u "¡Hola!" Para un literal de cadena codificada en UTF-16, por ejemplo)

Eche un vistazo a la más reciente C++0x draft (N2960).

2

Para su primera pregunta, esta es mi suposición.

La biblioteca IOStreams se construyó bajo un par de premisas con respecto a las codificaciones.Para convertir entre Unicode y otras codificaciones no tan habituales, por ejemplo, se supone que.

  • Dentro de su programa, debe usar una codificación de caracteres anchos (ancho fijo).
  • Solo el almacenamiento externo debe usar codificaciones multibyte (de ancho variable).

Creo que ese es el motivo de la existencia de las dos especializaciones de plantillas de std :: codecvt. Uno que mapea entre tipos de caracteres (quizás simplemente trabaje con ASCII) y otro que se asigna entre wchar_t (interno de su programa) y char (dispositivos externos). Por lo tanto, cuando necesite realizar una conversión a una codificación multibyte, debe hacerlo byte a byte. Tenga en cuenta que puede escribir una faceta que maneje el estado de codificación cuando lee/escribe cada byte desde/hacia la codificación multibyte.

Pensando de esta manera, el comportamiento del estándar de C++ es comprensible. Después de todo, está utilizando cadenas codificadas en ASCII de caracteres anchos (suponiendo que esta sea la configuración predeterminada en su plataforma y no haya cambiado de idioma). La conversión "natural" sería convertir cada carácter ASCII de caracteres anchos a un carácter ASCII ordinario (en este caso, un carácter). (La conversión existe y es sencilla.)

Por cierto, no estoy seguro si usted sabe, pero puede evitar esto creando una faceta que devuelva noconv para las conversiones. Entonces, tendrías tu archivo con caracteres anchos.

+0

Sus instalaciones probablemente no se mantendrán. UTF-16 es multibyte. La mayoría de las personas consideran UTF-32 como un desperdicio de datos de caracteres (no lo hago), así que terminaremos usando UTF-16 y tendremos todo el código adicional para manejar el caso de esquina especial de los pares de sustituto. –

+0

@Martin: UTF-8 y UTF-16 son todos multibyte. No dije que eran de ancho fijo. No entiendo exactamente lo que estás diciendo. –

3

mira esto: Class basic_filebuf

puede modificar el comportamiento predeterminado mediante el establecimiento de un char buffer amplia, utilizando pubsetbuf. Una vez que haya hecho eso, la salida será wchar_t y no char.

En otras palabras, por su ejemplo, usted tendrá:

wofstream file(L"Test.txt", ios_base::binary); //binary is important to set! 
wchar_t buffer[128]; 
file.rdbuf()->pubsetbuf(buffer, 128); 
file.put(0xFEFF); //this is the BOM flag, UTF16 needs this, but mirosoft's UNICODE doesn't, so you can skip this line, if any. 
file << someString; // the output file will consist of unicode characters! without the call to pubsetbuf, the out file will be ANSI (current regional settings) 
Cuestiones relacionadas