Подтвердить что ты не робот

Почему по умолчанию поток файлов в С++ имеет узкие письменные данные?

Честно говоря, я просто не получаю следующее дизайнерское решение в стандартной библиотеке С++. При написании больших символов в файл 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 или я что-то пропустил здесь?

4b9b3361

Ответ 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 для конверсий. Тогда у вас будет файл с широкими символами.