Насколько хорошо Юникод поддерживается в C ++ 11?

183

Я читал и слышал, что C ++ 11 поддерживает Unicode. Несколько вопросов по этому поводу:

  • Насколько хорошо стандартная библиотека C ++ поддерживает Unicode?
  • Делает std::stringто, что должен?
  • Как мне это использовать?
  • Где потенциальные проблемы?
Ральф Тандецки
источник
19
"Делает ли std :: string то, что должен?" Как вы думаете, что это должно сделать?
Р. Мартиньо Фернандес
2
Я использую utfcpp.sourceforge.net для моих нужд utf8. Это простой заголовочный файл, который предоставляет итераторы для строк Unicode.
Fscan
2
std :: string должен хранить байты, то есть последовательность кодовых единиц кодировки UTF-8. Да, именно так и есть, с самого начала. utf8everywhere.org
Павел Радзивиловский
3
Самые большие потенциальные проблемы с поддержкой Unicode заключаются в Unicode и его использовании в самих информационных технологиях. Unicode не подходит (и не предназначен) для того, для чего он используется. Unicode предназначен для воспроизведения каждого возможного глифа, который был написан кем-то кем-то, в какое-то время с каждым маловероятным и педантичным нюансом, включая 3 или 4 различных значения и 3 или 4 различных способа составления одного и того же символа. Оно не предназначено для того, чтобы быть полезным для использования в повседневном языке, и оно не предназначено для того, чтобы быть применимым или быть легко или однозначно обработанным.
Деймон
11
Да, он предназначен для использования на повседневном языке. Мой по крайней мере. И ваш, скорее всего, тоже. Просто получается, что обработка человеческого текста в общем виде - очень сложная задача. Невозможно даже однозначно определить, что такое персонаж. Общее воспроизведение глифа даже не является частью хартии Unicode.
Жан-Дени Мюйс

Ответы:

267

Насколько хорошо стандартная библиотека 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; стандарт допускает преобразования в обоих направлениях).

  • 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.

Р. Мартиньо Фернандес
источник
26
Чем больше я об этом читаю, тем больше у меня возникает чувство, что я ничего не понимаю во всем этом. Я прочитал большую часть этого материала пару месяцев назад и все еще чувствую, что снова и снова открываю для себя все это ... Чтобы не усложнять для моего бедного мозга, который сейчас немного болит, все эти советы на utf8everywhere все еще актуальны, право? Если я «просто» хочу, чтобы мои пользователи могли открывать и записывать файлы независимо от их системных настроек, я могу спросить у них имя файла, сохранить его в std :: string, и все должно работать правильно, даже в Windows? Извините, что спросил (снова) ...
Uflex
5
@Uflex Все, что вы действительно можете сделать с помощью std :: string, это обработать его как двоичный двоичный объект. В правильной реализации Unicode ни внутреннее (потому что оно скрыто глубоко в деталях реализации), ни внешнее кодирование не имеет значения (ну, конечно, вам все еще нужно иметь доступный кодер / декодер).
Cat Plus Plus
3
@ Уфлекс может быть. Я не знаю, если следующий совет вы не понимаете, это хорошая идея.
Р. Мартиньо Фернандес
1
Есть предложение по поддержке Unicode в C ++ 2014/17. Однако это 1, может быть, 4 года и мало пользы сейчас. open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html
graham.reeds
20
@ graham.reeds хаха, спасибо, но я знал об этом. Проверьте раздел «Благодарности»;)
Р. Мартиньо Фернандес
40

Unicode не поддерживается стандартной библиотекой (для любого разумного значения поддерживается).

std::stringне лучше , чем std::vector<char>: она совершенно не обращая внимания на Unicode (или любое другое представление / кодирование) и просто рассматривать его содержимое в виде сгустка байтов.

Если вам нужно хранить и хранить только капли , это работает довольно хорошо; но как только вы захотите использовать функциональность Unicode (количество точек кода , количество графем и т. д.), вам не повезло.

Единственная всеобъемлющая библиотека, о которой я знаю, это ICU . Интерфейс C ++ произошел от Java, поэтому он далеко не идиоматичен.

Матье М.
источник
2
Как насчет Boost.Locale ?
Uflex
11
@Uflex: со страницы, на которую вы ссылаетесь. Для достижения этой цели Boost.Locale использует современную библиотеку Unicode и локализации: ICU - Международные компоненты для Unicode.
Матье М.
1
Boost.Locale поддерживает другие бэкэнды не-ICU, см. Здесь: boost.org/doc/libs/1_53_0/libs/locale/doc/html/…
Superfly Jon,
@SuperflyJon: Да, но, согласно той же странице, поддержка Unicode для бэкэндов не-ICU "строго ограничена".
Матье М.
24

Вы можете безопасно хранить 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, который, вероятно, имеет много внутренних нулевых байтов, вам нужно изучить типы строк широких символов.

uckelman
источник
3
std::stringможет быть просто брошено в iostreams со встроенными нулями.
Р. Мартиньо Фернандес
3
Это полностью предназначено. Это не ломается c_str()вообще, потому что size()все еще работает. Только сломанные API (то есть те, которые не могут обрабатывать встроенные нули, как большая часть мира Си) ломаются.
Р. Мартиньо Фернандес
1
Внедренные нулевые значения прерываются, c_str()поскольку c_str()предполагается, что они возвращают данные в виде строки C с нулевым символом в конце - что невозможно из-за того, что строки C не могут иметь встроенные нули.
Uckelman
4
Уже нет. c_str()теперь просто возвращает то же самое, что data()и все. API, которые принимают размер, могут потреблять его. API, которые не, не могут.
Р. Мартиньо Фернандес
6
С небольшим отличием, которое c_str()гарантирует, что за результатом следует NUL-символоподобный объект, и я не думаю, что data()это так. Нет, похоже, data()сейчас тоже так делает. (Конечно, это не обязательно для API, которые используют размер вместо того, чтобы выводить его из поиска терминатора)
Бен Фойгт
8

C ++ 11 имеет несколько новых типов литеральных строк для Unicode.

К сожалению, поддержка в стандартной библиотеке для неоднородных кодировок (таких как UTF-8) все еще плоха. Например, нет хорошего способа получить длину (в кодовых точках) строки UTF-8.

Какой-то программист чувак
источник
Так нужно ли нам использовать std :: wstring для имен файлов, если мы хотим поддерживать нелатинские языки? Потому что новые строковые литералы здесь не очень помогают, так как строка обычно приходит от пользователя ...
Uflex
7
@Uflex std::stringможет содержать строку UTF-8 без проблем, но, например, lengthметод возвращает количество байтов в строке, а не количество кодовых точек.
Какой-то программист чувак
8
Честно говоря, получение длины в кодовых точках строки не имеет большого применения. Длина в байтах может использоваться, например, для правильного предварительного выделения буферов.
Р. Мартиньо Фернандес
2
Количество кодовых точек в строке UTF-8 не очень интересное число: можно написать ñ«LATIN SMALL LETTER N WITH TILDE» (U + 00F1) (то есть одна кодовая точка) или «LATIN SMALL LETTER N» ( U + 006E), за которым следует «КОМБИНИРОВАНИЕ ТИЛЬДЫ» (U + 0303), что составляет две кодовые точки.
Мартин Боннер поддерживает Монику
Все эти комментарии о том, что «вам это не нужно и вам не нужно», как «количество неважных кодовых точек» и т. Д., Звучат для меня немного странно. Как только вы напишите синтаксический анализатор, который должен анализировать исходный код utf8, дело доходит до спецификации синтаксического анализатора, считает ли он LATIN SMALL LETTER N' == (U+006E) followed by 'COMBINING TILDE' (U+0303).
BitTickler
4

Тем не менее, есть очень полезная библиотека называется крошечным-utf8 , который является в основном заменой для std::string/ std::wstring. Он призван восполнить пробел в еще отсутствующем классе контейнера utf8-string.

Это может быть наиболее удобным способом работы со строками utf8 (то есть без нормализации юникода и тому подобного). Вы комфортно работаете с кодовыми точками , в то время как ваша строка остается закодированной в кодированных charдлинах серий s.

Якоб Ридл
источник