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