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

Можно ли печатать строки 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'
что (): locale::facet::_S_create_c_locale имя недействительно

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

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

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

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

2 ответа

Я не использовал 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, если ваши стандарты кодирования допускают макросы.

Поддержка 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 доступен практически везде и фактически является стандартом де-факто, так что я не думаю, что это является серьезной проблемой.

Другие вопросы по тегам