Почему широкий файловый поток в C ++ сужает записанные данные по умолчанию? - PullRequest
18 голосов
/ 02 октября 2009

Честно говоря, я просто не понимаю следующее дизайнерское решение в стандартной библиотеке C ++. При записи широких символов в файл wofstream преобразует wchar_t в char символы:

#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!
}

Я знаю, что это связано со стандартом codecvt. codecvt для utf8 в Boost. Кроме того, здесь есть codecvt для utf16 от Мартина Йорка здесь на SO . Вопрос почему standard codecvt преобразует широкие символы? почему бы не написать символы такими, какие они есть!

Кроме того, мы получим unicode streams с C ++ 0x или я что-то здесь упустил?

Ответы [ 5 ]

13 голосов
/ 02 октября 2009

Очень частичный ответ на первый вопрос: файл представляет собой последовательность байтов, поэтому при работе с wchar_t необходимо не менее некоторое преобразование между wchar_t и char должно произойти. Чтобы сделать это преобразование «разумным», требуется знание кодировок символов, поэтому это преобразование может быть зависимым от локали в силу использования фасета в локали потока.

Тогда возникает вопрос, как сделать это преобразование в единственной локали, требуемой стандартом: «классической». На это нет «правильного» ответа, и поэтому стандарт очень расплывчат в этом. Из вашего вопроса я понимаю, что вы предполагаете, что слепое приведение (или memcpy () - ing) между wchar_t [] и char [] было бы хорошим способом. Это не является необоснованным, и фактически является тем, что (или, по крайней мере, было) сделано в некоторых реализациях.

Еще одно POV может заключаться в том, что поскольку codecvt является аспектом языкового стандарта, разумно ожидать, что преобразование будет выполнено с использованием «кодирования языкового стандарта» (здесь я неуклюжий, поскольку концепция довольно размыта). Например, можно ожидать, что турецкий язык будет использовать ISO-8859-9, или японец, который будет использовать Shift JIS. По схожести, «классическая» локаль конвертируется в эту «кодировку локали». Очевидно, Microsoft решила просто урезать (что приводит к IS-8859-1, если мы предполагаем, что wchar_t представляет UTF-16 и что мы остаемся в основной многоязычной плоскости), в то время как реализация Linux, о которой я знаю, решила придерживаться ASCII.

Ваш второй вопрос:

Кроме того, мы получим реальные потоки Unicode с C ++ 0x или я что-то здесь упускаю?

В разделе [locale.codecvt] n2857 (последний имеющийся у меня проект C ++ 0x) можно прочитать:

Специализация codecvt<char16_t, char, mbstate_t> преобразует схемы кодирования UTF-16 и UTF-8, а специализация codecvt <char32_t, char, mbstate_t> преобразует схемы кодирования UTF-32 и UTF-8. codecvt<wchar_t,char,mbstate_t> конвертирует между собственными наборами символов для узких и широких символов.

В разделе [locale.stdcvt] мы находим:

Для фасета codecvt_utf8: - Фасет должен преобразовывать между многобайтовыми последовательностями UTF-8 и UCS2 или UCS4 (в зависимости от размера Elem) в программе. [...]

Для фасета codecvt_utf16: - Фасет должен преобразовывать между многобайтовыми последовательностями UTF-16 и UCS2 или UCS4 (в зависимости от размера Elem) в программе. [...]

Для фасета codecvt_utf8_utf16: - Фасет должен преобразовывать между многобайтовыми последовательностями UTF-8 и UTF-16 (один или два 16-битных кода) в программе.

Так что я предполагаю, что это означает «да», но вам нужно быть более точным в том, что вы подразумеваете под «реальными потоками Юникода», чтобы быть уверенным.

7 голосов
/ 02 октября 2009

Модель, используемая C ++ для кодировок, унаследована от C и датируется как минимум 1989 годом.

Два основных момента:

  • IO выполняется в терминах char.
  • задача локали - определить, насколько широкие символы сериализуются
  • языковой стандарт по умолчанию (названный "C") очень минимален (я не помню ограничений из стандарта, здесь он может обрабатывать только 7-битный ASCII как узкий и широкий набор символов).
  • существует локаль, определенная для среды с именем ""

Таким образом, чтобы получить что-либо, вы должны установить локаль.

Если я использую простую программу

#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";
    }
}

, которые используют локаль среды и выводят широкий символ кода 0x00FF в файл. Если я попрошу использовать локаль "C", я получу

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

языковой стандарт не смог обработать широкий символ, и мы получили уведомление о проблеме при сбое ввода-вывода. Если я бегу, спрашиваю локаль UTF-8, я получаю

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

(od -t x1 просто выведите файл, представленный в шестнадцатеричном формате), именно то, что я ожидаю для файла в кодировке UTF-8.

3 голосов
/ 02 октября 2009

Я не знаю о wofstream. Но C ++ 0x будет включать в себя новые типы символов округа (char16_t, char32_t) с гарантированной шириной и подписью (без знака), которые могут использоваться для UTF-8, UTF-16 и UTF-32. Кроме того, появятся новые строковые литералы (например, «Hello!» Для строкового литерала с кодировкой UTF-16)

Проверьте самые последние C ++ 0x черновик (N2960) .

2 голосов
/ 12 августа 2010

Проверьте это: Класс basic_filebuf

Вы можете изменить поведение по умолчанию, установив буфер wide char, используя pubsetbuf. Как только вы это сделаете, вывод будет wchar_t, а не char.

Другими словами, для вашего примера у вас будет:

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)  
2 голосов
/ 02 октября 2009

По вашему первому вопросу, это мое предположение.

Библиотека IOStreams была построена под парой предпосылок относительно кодировок. Например, для преобразования между Unicode и другими не очень обычными кодировками предполагается, что.

  • Внутри вашей программы вы должны использовать (широкополосную) кодировку широких символов.
  • Только внешнее хранилище должно использовать (переменная ширина) многобайтовые кодировки.

Я считаю, что это является причиной существования двух шаблонных специализаций std :: codecvt. Один, который отображается между типами символов (возможно, вы просто работаете с ASCII), а другой - между wchar_t (внутренним для вашей программы) и char (внешними устройствами). Поэтому, когда вам нужно выполнить преобразование в многобайтовую кодировку, вы должны делать это побайтово. Обратите внимание, что вы можете написать фасет, который обрабатывает состояние кодирования при чтении / записи каждого байта из / в многобайтовую кодировку.

При таком подходе поведение стандарта C ++ понятно. В конце концов, вы используете широкие символы в кодировке ASCII (при условии, что это значение по умолчанию на вашей платформе, и вы не переключали локали) строк. «Естественным» преобразованием будет преобразование каждого широкосимвольного символа ASCII в обычный (в данном случае один символ) символ ASCII. (Преобразование существует и является простым.)

Кстати, я не уверен, знаете ли вы, но вы можете избежать этого, создав фасет, который возвращает noconv для преобразований. Тогда у вас будет файл с широкими символами.

...