Я читал и слышал, что C ++ 11 поддерживает Unicode. Несколько вопросов по этому поводу:
- Насколько хорошо стандартная библиотека C ++ поддерживает Unicode?
- Делает
std::string
то, что должен? - Как мне это использовать?
- Где потенциальные проблемы?
Я читал и слышал, что C ++ 11 поддерживает Unicode. Несколько вопросов по этому поводу:
std::string
то, что должен?Ответы:
Насколько хорошо стандартная библиотека 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
.
Библиотека локализации
Библиотека локализации по-прежнему считает, что один из этих «похожих на символы» объектов равен одному «символу». Это, конечно, глупо и делает невозможным правильную работу многих вещей, кроме небольшого подмножества Юникода, такого как 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; стандарт допускает преобразования в обоих направлениях).
codecvt_utf8<char16_t>
и codecvt_utf8<wchar_t>
где sizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
и codecvt_utf8<wchar_t>
где sizeof(wchar_t) == 4
;codecvt_utf16<char16_t>
и codecvt_utf16<wchar_t>
где sizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
и codecvt_utf16<wchar_t>
где sizeof(wchar_t) == 4
;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*
, в этом отношении), потому что Unicode NUL (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, который, вероятно, имеет много внутренних нулевых байтов, вам нужно изучить типы строк широких символов.
std::string
может быть просто брошено в iostreams со встроенными нулями.
c_str()
вообще, потому что size()
все еще работает. Только сломанные API (то есть те, которые не могут обрабатывать встроенные нули, как большая часть мира Си) ломаются.
c_str()
поскольку c_str()
предполагается, что они возвращают данные в виде строки C с нулевым символом в конце - что невозможно из-за того, что строки C не могут иметь встроенные нули.
c_str()
теперь просто возвращает то же самое, что data()
и все. API, которые принимают размер, могут потреблять его. API, которые не, не могут.
c_str()
гарантирует, что за результатом следует NUL-символоподобный объект, и я не думаю, что data()
это так. Нет, похоже, data()
сейчас тоже так делает. (Конечно, это не обязательно для API, которые используют размер вместо того, чтобы выводить его из поиска терминатора)
C ++ 11 имеет несколько новых типов литеральных строк для Unicode.
К сожалению, поддержка в стандартной библиотеке для неоднородных кодировок (таких как UTF-8) все еще плоха. Например, нет хорошего способа получить длину (в кодовых точках) строки UTF-8.
std::string
может содержать строку UTF-8 без проблем, но, например, length
метод возвращает количество байтов в строке, а не количество кодовых точек.
ñ
«LATIN SMALL LETTER N WITH TILDE» (U + 00F1) (то есть одна кодовая точка) или «LATIN SMALL LETTER N» ( U + 006E), за которым следует «КОМБИНИРОВАНИЕ ТИЛЬДЫ» (U + 0303), что составляет две кодовые точки.
LATIN SMALL LETTER N'
== (U+006E) followed by 'COMBINING TILDE' (U+0303)
.
Тем не менее, есть очень полезная библиотека называется крошечным-utf8 , который является в основном заменой для std::string
/ std::wstring
. Он призван восполнить пробел в еще отсутствующем классе контейнера utf8-string.
Это может быть наиболее удобным способом работы со строками utf8 (то есть без нормализации юникода и тому подобного). Вы комфортно работаете с кодовыми точками , в то время как ваша строка остается закодированной в кодированных char
длинах серий s.