Как напечатать строки UTF-8 без использования специфических для платформы функций? - PullRequest
3 голосов
/ 09 февраля 2012

Можно ли печатать строки UTF-8 без использования специальных функций платформы?

#include <iostream>
#include <locale>
#include <string>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    wcout.imbue(locale("en_US.UTF-8")); // broken on Windows (?)

    wstring ws1 = L"Wide string.";
    wstring ws2 = L"Wide string with special chars \u20AC";  // Euro character

    wcout << ws1 << endl;
    wcout << ws2 << endl;
    wcout << ws1 << endl;
}

Я получаю эту ошибку во время выполнения:

прекращение вызова после выброса экземпляра 'std :: runtime_error'
what (): locale :: facet :: _ S_create_c_locale имя недействительно

Если я уберу строку wcout.imbue(locale("en_US.UTF-8"));, я напечатаю только ws1 и только один раз.

В другом вопросе (« Как мне записать и обработать какой-нибудь текст в Unicode? ») Филипп пишет: «wcin и wcout не работают в Windows, как и эквивалентные функции языка Си. Работает только нативный API». Это тоже правда из MinGW?

Спасибо за любую подсказку!

Платформа:
MinGW / GCC
Windows 7

Ответы [ 2 ]

5 голосов
/ 10 февраля 2012

Я не использовал gcc в среде mingw в Windows, но, насколько я понимаю, он не поддерживает локали C ++.

Так как он не поддерживает локали C ++, это не очень актуально, но, к вашему сведению, Windows не использует ту же схему именования локалей, что и большинство других платформ. Они используют аналогичную language_country.encoding, но язык и страна не являются кодами, а кодировка - это номер кодовой страницы Windows. Таким образом, языковым стандартом будет «English_United States.65001», однако это не поддерживаемая комбинация (кодовая страница 65001 (UTF-8) не поддерживается как часть какого-либо языкового стандарта).

Причина, по которой печатается только ws1, и только один раз, заключается в том, что при печати символа \u20AC происходит сбой потока и устанавливается бит сбоя. Вы должны устранить ошибку, прежде чем что-либо будет напечатано.


C ++ 11 представил некоторые вещи, которые будут иметь дело с UTF-8, но пока не все поддерживается, и дополнения не полностью решают проблему. Но вот как сейчас обстоят дела:

Когда char16_t и char32_t поддерживаются в VS как собственные типы, а не как typedefs, вы сможете использовать стандартные специализации фасетов codecvt codecvt<char16_t,char,mbstate_t> и codecvt<char32_t,char,mbstate_t>, которые необходимы для преобразования между UTF-16 или UTF -32 соответственно и UTF-8 (а не кодировка выполнения или кодировка системы). Это пока не работает, потому что в текущей VS (и в VS11DP) эти типы являются только typedefs, а специализации шаблонов не работают с typedefs, но код уже находится в заголовках в VS 2010, просто защищен #ifdef .

Стандарт также определяет некоторые поддерживаемые шаблоны фасетов codecvt специального назначения, codecvt_utf8 и codecvt_utf8_utf16. Первый преобразует между UTF-8 и UCS-2 или UCS-4 в зависимости от размера используемого вами широкого символа, а второй преобразует между кодовыми единицами UTF-8 и UTF-16 независимо от размера широкого символа. типа.

std::wcout.imbue(std::locale(std::locale::classic(),new std::codecvt_utf8_utf16<wchar_t>()));
std::wcout << L"ØÀéîðüýþ\n";

Это выведет кодовые единицы UTF-8 через все, что подключено к wcout. Если вывод был перенаправлен в файл, то при открытии он покажет файл в кодировке UTF-8. Однако , из-за модели консоли в Windows и способа реализации стандартных потоков, вы не получите правильное отображение символов Unicode в командной строке таким образом (даже если вы установите кодовую страницу вывода консоли до UTF-8 с SetConsoleOutputCP(CP_UTF8)). Модули кода UTF-8 выводятся по одному за раз, и консоль будет просматривать каждый отдельный передаваемый ей чанк, ожидая, что каждый переданный чанк (т.е. в данном случае один байт) будет полным и действительным кодированием. Неполные или недопустимые последовательности в чанке (в этом случае каждый байт всех многобайтовых символьных представлений) будут заменены на U + FFFD.

Если вместо использования iostreams вы используете функцию C puts для записи всей строки в кодировке UTF-8 (и если кодовая страница вывода консоли установлена ​​правильно), то вы можете напечатать строку UTF-8 и получить ее отображается в консоли. Для этого можно использовать те же аспекты codecvt с некоторыми другими классами C ++ 11:

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> convert;
puts(convert(L"ØÀéîðüýþ\n).to_bytes().c_str());

Выше все еще не вполне переносимо, поскольку предполагается, что wchar_t - это UTF-16, что имеет место в Windows, но не на большинстве других платформ, и это не требуется стандартом. (На самом деле я понимаю, что это технически не соответствует, потому что UTF-16 требуется несколько единиц кода для представления некоторых символов, а стандарт требует, чтобы все символы в выбранной кодировке были представлены в одном wchar_t).

std::wstring_convert<std::codecvt_utf8<wchar_t>,wchar_t> convert;

Вышеуказанное будет переносимо для UCS-4 и USC-2, но не будет работать вне Базовой многоязычной плоскости на платформах, использующих UTF-16.

Вы можете использовать черту типа conditional для выбора между этими двумя аспектами в зависимости от размера wchar_t и получить то, что в основном работает:

std::wstring_convert<
    std::conditional<sizeof(wchar_t)==2,std::codecvt_utf8_utf16<wchar_t>,
                                        std::codecvt_utf8<wchar_t>
    >::type,
    wchar_t
> convert;

Или просто используйте макросы препроцессора для определения соответствующего typedef, если ваши стандарты кодирования допускают макросы.

1 голос
/ 10 февраля 2012

Поддержка Windows для UTF-8 довольно слабая, и хотя это возможно сделать с помощью Windows API, это совсем не весело, также ваш вопрос указывает на то, что вы НЕ хотите использовать функции, специфичные для платформы ...

Что касается выполнения в «стандартном C ++», я не уверен, возможно ли это в Windows без кода для конкретной платформы.ОДНАКО, существует множество сторонних библиотек, которые абстрагируют эти детали платформы и позволяют писать переносимый код.

Недавно я обновил свои приложения для внутреннего использования UTF-8 с помощью Boost.Locale.библиотека.http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html

Его класс генерации локали позволит вам сгенерировать объект локали на основе UTF-8, который вы затем сможете внедрить во все стандартные потоки и т. Д.

Я использую это прямо сейчас ви MSVC, и GCC через MinGW-w64 успешно!Я настоятельно рекомендую вам проверить это.Да, к сожалению, технически это не «стандартный C ++», однако Boost доступен практически везде, и фактически является стандартом де-факто, поэтому я не думаю, что это огромная проблема.

...