Насколько хорошо Unicode поддерживается в C++11?
Я читал и слышал, что C++11 поддерживает Unicode. Несколько вопросов по этому поводу:
- Насколько хорошо стандартная библиотека C++ поддерживает Unicode?
- Есть ли
std::string
делать то, что должно? - Как мне это использовать?
- Где потенциальные проблемы?
5 ответов
Насколько хорошо стандартная библиотека C++ поддерживает Unicode?
Жутко.
Быстрый просмотр библиотечных средств, которые могут обеспечить поддержку Unicode, дает мне этот список:
- Библиотека строк
- Библиотека локализации
- Библиотека ввода / вывода
- Библиотека регулярных выражений
Я думаю, что все, кроме первого, оказывают ужасную поддержку. Я вернусь к этому более подробно после короткого обхода ваших других вопросов.
Есть ли
std::string
делать то, что должно?
Да. Согласно стандарту C++, это то, что std::string
и его братья и сестры должны сделать:
Шаблон класса
basic_string
описывает объекты, которые могут хранить последовательность, состоящую из различного числа произвольных подобных чарсу объектов, причем первый элемент последовательности находится в нулевой позиции.
Что ж, std::string
делает это просто отлично Предоставляет ли это какую-либо специфическую для Unicode функциональность? Нет.
Должно ли это? Возможно нет. std::string
хорошо, как последовательность char
объекты. Это полезно; Единственное раздражение в том, что это очень низкоуровневое представление текста, а стандартный C++ не обеспечивает более высокого уровня.
Как мне это использовать?
Используйте это как последовательность char
объекты; притворяться, что это что-то еще, должно закончиться болью.
Где потенциальные проблемы?
Повсюду? Посмотрим...
Библиотека строк
Библиотека строк предоставляет нам basic_string
, который является просто последовательностью того, что стандарт называет "объектами, подобными символу". Я называю их кодовыми единицами. Если вы хотите просмотреть текст на высоком уровне, это не то, что вы ищете. Это вид текста, подходящего для сериализации / десериализации / хранения.
Он также предоставляет некоторые инструменты из библиотеки C, которые можно использовать для преодоления разрыва между узким миром и миром Unicode: c16rtomb
/ mbrtoc16
а также c32rtomb
/ mbrtoc32
,
Библиотека локализации
Библиотека локализации по-прежнему считает, что один из этих "похожих на символы" объектов равен одному "символу". Это, конечно, глупо и делает невозможным правильную работу многих вещей за пределами небольшого подмножества Unicode, такого как ASCII.
Рассмотрим, например, что стандарт называет "удобными интерфейсами" в <locale>
заголовок:
template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
Как вы ожидаете, что любая из этих функций будет должным образом классифицировать, скажем, U+1F34C as, как в u8""
или же u8"\U0001F34C"
? Нет никакого способа, которым это когда-либо будет работать, потому что эти функции принимают только одну единицу кода в качестве ввода.
Это может работать с соответствующей локалью, если вы использовали char32_t
только: U'\U0001F34C'
является единым кодовым блоком в UTF-32.
Тем не менее, это все еще означает, что вы получаете только простые преобразования обсадной колонны с toupper
а также tolower
что, например, недостаточно для некоторых немецких языков: от "ß" до "SS"☦, но toupper
может вернуть только одну единицу кода символа.
Следующий, wstring_convert
/ wbuffer_convert
и стандартные аспекты преобразования кода.
wstring_convert
используется для преобразования между строками в одной заданной кодировке в строки в другой заданной кодировке. В этом преобразовании участвуют два типа строк, которые в стандарте называются байтовой строкой и широкой строкой. Поскольку эти термины действительно вводят в заблуждение, я предпочитаю использовать "сериализованный" и "десериализованный" соответственно †.
Кодировки для преобразования определяются с помощью codecvt (фасета преобразования кода), передаваемого в качестве аргумента типа шаблона wstring_convert
,
wbuffer_convert
выполняет аналогичную функцию, но как широкий десериализованный потоковый буфер, который упаковывает байтовый сериализованный буфер потока. Любой ввод / вывод выполняется через базовый байтовый сериализованный потоковый буфер с преобразованиями в и из кодировок, заданных аргументом codecvt. Запись сериализуется в этот буфер, а затем записывает из него, а чтение читает в буфер и затем десериализуется из него.
Стандарт предоставляет некоторые шаблоны классов codecvt для использования с этими средствами: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
, и немного codecvt
специализаций. Вместе эти стандартные аспекты обеспечивают все следующие преобразования. (Примечание: в следующем списке кодировка слева всегда является сериализованной строкой /streambuf, а кодировка справа всегда десериализованной строкой /streambuf; стандарт допускает преобразования в обоих направлениях).
- UTF-8 ↔ UCS-2 с
codecvt_utf8<char16_t>
, а такжеcodecvt_utf8<wchar_t>
гдеsizeof(wchar_t) == 2
; - UTF-8 ↔ UTF-32 с
codecvt_utf8<char32_t>
,codecvt<char32_t, char, mbstate_t>
, а такжеcodecvt_utf8<wchar_t>
гдеsizeof(wchar_t) == 4
; - UTF-16 ↔ UCS-2 с
codecvt_utf16<char16_t>
, а такжеcodecvt_utf16<wchar_t>
гдеsizeof(wchar_t) == 2
; - UTF-16 ↔ UTF-32 с
codecvt_utf16<char32_t>
, а такжеcodecvt_utf16<wchar_t>
гдеsizeof(wchar_t) == 4
; - UTF-8 ↔ UTF-16 с
codecvt_utf8_utf16<char16_t>
,codecvt<char16_t, char, mbstate_t>
, а такжеcodecvt_utf8_utf16<wchar_t>
гдеsizeof(wchar_t) == 2
; - узкий, широкий с
codecvt<wchar_t, char_t, mbstate_t>
- не с
codecvt<char, char, mbstate_t>
,
Некоторые из них полезны, но здесь есть много неловких вещей.
Прежде всего - святой верховный суррогат! эта схема именования грязная.
Затем есть большая поддержка UCS-2. UCS-2 - это кодировка из Unicode 1.0, которая была заменена в 1996 году, потому что она поддерживает только базовую многоязычную плоскость. Почему комитет счел желательным сосредоточиться на кодировке, которая была заменена более 20 лет назад, я не знаю ‡. Не то, чтобы поддержка большего количества кодировок была плохой или что-то в этом роде, но UCS-2 появляется здесь слишком часто.
Я бы сказал, что char16_t
очевидно предназначен для хранения кодовых блоков UTF-16. Тем не менее, это одна часть стандарта, которая считает иначе. codecvt_utf8<char16_t>
не имеет ничего общего с UTF-16. Например, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
будет хорошо скомпилироваться, но безоговорочно потерпит неудачу: вход будет обрабатываться как строка UCS-2 u"\xD83C\xDF4C"
, который не может быть преобразован в UTF-8, потому что UTF-8 не может кодировать любое значение в диапазоне 0xD800-0xDFFF.
Тем не менее, на фронте UCS-2 нет способа чтения из потока байтов UTF-16 в строку UTF-16 с этими аспектами. Если у вас есть последовательность байтов UTF-16, вы не можете десериализовать ее в строку char16_t
, Это удивительно, потому что это более или менее преобразование личности. Еще более удивительным является тот факт, что существует поддержка десериализации из потока UTF-16 в строку UCS-2 с codecvt_utf16<char16_t>
, что на самом деле конверсия с потерями.
Тем не менее, поддержка UTF-16 в качестве байтов довольно хороша: она поддерживает обнаружение бесконечности в спецификации или ее явное выделение в коде. Он также поддерживает создание вывода с и без спецификации.
Есть еще несколько интересных возможностей для конвертации. Невозможно десериализовать поток байтов или строку UTF-16 в строку UTF-8, поскольку UTF-8 никогда не поддерживается в качестве десериализованной формы.
И здесь узкий / широкий мир полностью отделен от мира UTF/UCS. Не существует преобразований между узкими / широкими кодировками старого стиля и любыми кодировками Unicode.
Библиотека ввода / вывода
Библиотека ввода / вывода может использоваться для чтения и записи текста в кодировках Unicode с использованием wstring_convert
а также wbuffer_convert
Услуги, описанные выше. Я не думаю, что есть еще что-то, что должно было бы поддерживаться этой частью стандартной библиотеки.
Библиотека регулярных выражений
Ранее я уже разъяснял проблемы с регулярными выражениями в C++ и Unicode в Stack Overflow. Я не буду повторять все эти пункты здесь, а просто скажу, что регулярные выражения C++ не имеют поддержки Unicode уровня 1, что является минимальным условием для их использования, не прибегая к повсеместному использованию UTF-32.
Это оно?
Да это оно. Это существующий функционал. Существует множество функций Unicode, которые нигде не встречаются, такие как алгоритмы нормализации или сегментации текста.
U + 1F4A9. Есть ли способ получить лучшую поддержку Unicode в C++?
Обычные подозреваемые: ICU и Boost.Locale.
† Неудивительно, что строка байтов - это строка байтов, т.е. char
объекты. Однако, в отличие от широкого строкового литерала, который всегда является массивом wchar_t
объекты, "широкая строка" в этом контексте не обязательно является строкой wchar_t
объекты. На самом деле, стандарт никогда не определяет явно, что означает "широкая строка", поэтому нам остается только угадывать значение от использования. Поскольку стандартная терминология небрежная и запутанная, я использую свою собственную во имя ясности.
Кодировки, подобные UTF-16, могут храниться в виде последовательностей char16_t
, которые тогда не имеют порядка байтов; или они могут быть сохранены как последовательности байтов, которые имеют порядковый номер (каждая последовательная пара байтов может представлять разные char16_t
значение в зависимости от порядка байтов). Стандарт поддерживает обе эти формы. Последовательность char16_t
более полезно для внутренних манипуляций в программе. Последовательность байтов - это способ обмена такими строками с внешним миром. Термины, которые я буду использовать вместо "байт" и "широкий", таким образом, "сериализуются" и "десериализуются".
‡ Если вы собираетесь сказать "но Windows!" держи свой , Все версии Windows начиная с Windows 2000 используют UTF-16.
☦ Да, я знаю про grosses Eszett (ẞ), но даже если бы вы за один вечер поменяли все немецкие языки на прописные буквы ß, есть еще множество других случаев, когда это не получится. Попробуйте прописные буквы U+FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. Там нет ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; это только заглавные буквы к двум Fs. Или U+01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; нет заранее составленного капитала; это только заглавные буквы к заглавной букве J и объединяющему caron.
Unicode не поддерживается стандартной библиотекой (для любого разумного значения поддерживается).
std::string
не лучше чем std::vector<char>
: он полностью игнорирует Unicode (или любое другое представление / кодировку) и просто обрабатывает его содержимое как блок байтов.
Если вам нужно только хранить и катать капли, это работает довольно хорошо; но как только вы захотите использовать функциональность Unicode (количество точек кода, количество графем, ...), вам не повезло.
Единственная обширная библиотека, о которой я знаю, это ICU. Интерфейс C++ произошел от Java, поэтому он далеко не идиоматичен.
Вы можете безопасно хранить UTF-8 в std::string
(или в char[]
или же char*
(в этом отношении), из-за того, что NUL Unicode (U+0000) является нулевым байтом в UTF-8 и что это единственный способ, которым нулевой байт может появиться в UTF-8. Следовательно, ваши строки UTF-8 будут должным образом завершены в соответствии со всеми строковыми функциями C и C++, и вы можете перебирать их с помощью iostreams C++ (включая std::cout
а также std::cerr
, пока ваша локаль UTF-8).
Что вы не можете сделать с std::string
для UTF-8 - получить длину в кодовых точках. std::string::size()
сообщит вам длину строки в байтах, которая равна только числу кодовых точек, когда вы находитесь в подмножестве ASCII UTF-8.
Если вам нужно работать со строками UTF-8 на уровне кодовой точки - не просто хранить и распечатывать их - или если вы имеете дело с UTF-16, который, вероятно, имеет много внутренних нулевых байтов, вам нужно смотреть на типы строк широких символов.
В C++11 есть пара новых типов литеральных строк для Unicode.
К сожалению, поддержка в стандартной библиотеке для неоднородных кодировок (таких как UTF-8) все еще плоха. Например, нет хорошего способа получить длину (в кодовых точках) строки UTF-8.
Тем не менее, есть довольно полезная библиотека с именем tiny-utf8, которая является заменой std::string
/std::wstring
, Он призван восполнить пробел в еще отсутствующем классе контейнера utf8-string.
Это может быть наиболее удобным способом работы со строками utf8 (то есть без нормализации юникода и тому подобного). Вы комфортно работаете с кодовыми точками, в то время как ваша строка остается закодированной в кодировке длины пробега char
s.