Почему по умолчанию поток файлов в С++ имеет узкие письменные данные?
Честно говоря, я просто не получаю следующее дизайнерское решение в стандартной библиотеке С++. При написании больших символов в файл 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
. Для utf8
существует codecvt
в Boost
. Кроме того, существует codecvt
для utf16
от Мартина Йорка здесь, на SO. Вопрос в том, почему standard codecvt
преобразует широкие символы? почему бы не написать символы, как они есть!
Кроме того, мы собираемся получить реальный unicode streams
с С++ 0x или я что-то пропустил здесь?
Ответы
Ответ 1
Модель, используемая С++ для кодировок, наследуется от 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
Локаль не смог обработать широкий символ, и мы получили уведомление о проблеме по мере того, как IO завершился с ошибкой. Если я запустил запрос 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.
Ответ 2
Очень частный ответ для первого вопроса: Файл представляет собой последовательность байтов, поэтому при работе с wchar_t
, должно произойти хотя бы некоторое преобразование между wchar_t
и char
. Чтобы сделать это преобразование "разумным", необходимо знание кодировок символов, поэтому именно поэтому это преобразование разрешено зависящим от языка, в силу использования грани в локали потока.
Затем возникает вопрос, как это преобразование должно быть сделано в единственном регионе, требуемом стандартом: "классическом". Для этого нет "правильного" ответа, и, следовательно, стандарт очень смутно. Я понимаю из вашего вопроса, что вы предполагаете, что слепое кастинг (или memcpy()) между wchar_t [] и char [] был бы хорошим способом. Это не является необоснованным, и на самом деле то, что (или, по крайней мере, было) сделано в некоторых реализациях.
Другим POV будет то, что, поскольку codecvt является языковым аспектом, разумно ожидать, что преобразование будет выполнено с использованием "кодировки локали" (я здесь довольно ручной, так как концепция довольно нечеткая). Например, можно было бы ожидать, что турецкая локализация будет использовать ISO-8859-9 или японский язык для использования Shift JIS. По сходству "классический" язык преобразуется в эту "кодировку локали". По-видимому, Microsoft решила просто обрезать (что приводит к IS-8859-1, если мы предполагаем, что wchar_t
представляет UTF-16 и что мы остаемся в базовой многоязычной плоскости), а реализация Linux, о которой я знаю, решила придерживаться ASCII.
Для вашего второго вопроса:
Кроме того, мы собираемся получать реальные потоки unicode с С++ 0x или я что-то пропустил здесь?
В разделе [locale.codecvt] n2857 (последний проект С++ 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-разрядных кода) внутри программы.
Поэтому я предполагаю, что это означает "да", но вам нужно быть более точным о том, что вы подразумеваете под "реальными потоками Unicode".
Ответ 3
Я не знаю о wofstream. Но С++ 0x будет включать в себя новые типы символов (char16_t, char32_t) гарантированной ширины и подписи (без знака), которые могут быть портативно использованы для UTF-8, UTF-16 и UTF-32. Кроме того, будут появляться новые строковые литералы (u "Hello!" Для кодированного строкового литерала UTF-16, например)
Ознакомьтесь с последним С++ 0x draft (N2960).
Ответ 4
Проверьте это:
Класс basic_filebuf
Вы можете изменить поведение по умолчанию, установив широкий буфер 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 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)
Ответ 5
Для вашего первого вопроса, это моя догадка.
Библиотека IOStreams была построена в нескольких помещениях по кодированию. Например, для преобразования между Unicode и другими нестандартными кодировками он предположил, что.
- Внутри вашей программы вы должны использовать широкоформатную кодировку с фиксированной шириной.
- Только внешнее хранилище должно использовать (широтно-переменные) многобайтовые кодировки.
Я считаю, что это является причиной существования двух шаблонных специализаций std:: codecvt. Один, который отображает типы char (возможно, вы просто работаете с ASCII), а другой, который отображает между wchar_t (внутри вашей программы) и char (внешние устройства). Поэтому всякий раз, когда вам нужно выполнить преобразование в многобайтовую кодировку, вы должны делать это побайтно. Обратите внимание, что вы можете написать фасет, который обрабатывает состояние кодирования при чтении/записи каждого байта с/на многобайтовое кодирование.
Размышляя таким образом, поведение стандарта С++ понятно. В конце концов, вы используете широкоформатный ASCII-кодированный (при условии, что это по умолчанию на вашей платформе и вы не переключили локальные строки). "Естественным" преобразованием было бы преобразование каждого широкосимвольного символа ASCII в обычный (в данном случае один символ char) ASCII. (Конвертация существует и проста.)
Кстати, я не уверен, знаете ли вы, но вы можете избежать этого, создав грань, которая возвращает noconv для конверсий. Тогда у вас будет файл с широкими символами.