Что "не так" с C++ wchar_t и wstrings? Какие есть альтернативы широким символам?
Я видел много людей в сообществе C++ (особенно ## C++ на freenode), возмущенных использованием wstrings
а также wchar_t
и их использование в Windows API. Что именно "не так" с wchar_t
а также wstring
и если я хочу поддержать интернационализацию, какие есть альтернативы широким символам?
3 ответа
Что такое wchar_t?
wchar_t определено так, что кодировка char любой локали может быть преобразована в представление wchar_t, где каждый wchar_t представляет ровно одну кодовую точку:
Тип wchar_t - это отдельный тип, значения которого могут представлять разные коды для всех членов наибольшего расширенного набора символов, указанного среди поддерживаемых локалей (22.3.1).
- C++ [basic.fundamental] 3.9.1/5
Это не требует, чтобы wchar_t был достаточно большим, чтобы представлять любой символ из всех локалей одновременно. То есть кодировка, используемая для wchar_t, может отличаться в разных локалях. Это означает, что вы не можете обязательно преобразовать строку в wchar_t, используя одну локаль, а затем преобразовать обратно в char, используя другую локаль. 1
Поскольку использование wchar_t в качестве общего представления для всех локалей, по-видимому, является основным использованием wchar_t на практике, вы можете задаться вопросом, для чего это хорошо, если нет.
Первоначальная цель и цель wchar_t состояла в том, чтобы сделать текстовую обработку простой, определяя ее так, чтобы она требовала однозначного сопоставления кодовых единиц строки с символами текста, что позволяет использовать те же простые алгоритмы, которые используются со строками ascii для работы с другими языками.
К сожалению, формулировка спецификации wchar_t предполагает взаимно-однозначное сопоставление символов и кодовых точек для достижения этой цели. Unicode нарушает это предположение 2, поэтому вы не можете безопасно использовать wchar_t для простых текстовых алгоритмов.
Это означает, что переносимое программное обеспечение не может использовать wchar_t ни в качестве общего представления для текста между локалями, ни для обеспечения возможности использования простых текстовых алгоритмов.
Какая польза от wchar_t сегодня?
Не так много, для переносимого кода в любом случае. Если __STDC_ISO_10646__
определяется, тогда значения wchar_t напрямую представляют кодовые точки Unicode с одинаковыми значениями во всех локалях. Это делает безопасным выполнение преобразований между локалями, упомянутых ранее. Однако вы не можете полагаться только на него, чтобы решить, что вы можете использовать wchar_t таким образом, потому что, хотя большинство платформ Unix определяют его, Windows не делает этого, хотя Windows использует один и тот же языковой стандарт wchar_t во всех языковых стандартах.
Причина, по которой Windows не определяет __STDC_ISO_10646__
потому что Windows использует UTF-16 в качестве кодировки wchar_t, и потому что UTF-16 использует суррогатные пары для представления кодовых точек, больших, чем U+FFFF, что означает, что UTF-16 не удовлетворяет требованиям для __STDC_ISO_10646__
,
Для конкретного кода платформы wchar_t может быть более полезным. Это по сути требуется в Windows (например, некоторые файлы просто не могут быть открыты без использования имен файлов wchar_t), хотя, насколько я знаю, Windows является единственной платформой, где это верно (поэтому, возможно, мы можем думать о wchar_t как о Windows_char_t).
В ретроспективе wchar_t явно не полезен для упрощения обработки текста или для хранения независимого от локали текста. Переносимый код не должен пытаться использовать его для этих целей. Непереносимый код может оказаться полезным просто потому, что это требуется для некоторых API.
альтернативы
Мне нравится альтернатива - использовать C-строки в кодировке UTF-8, даже на платформах, не особенно дружественных к UTF-8.
Таким образом, можно написать переносимый код, используя общее текстовое представление на разных платформах, использовать стандартные типы данных по назначению, получить поддержку языка для этих типов (например, строковые литералы, хотя некоторые приемы необходимы, чтобы он работал для некоторых компиляторов), некоторые поддержка стандартной библиотеки, поддержка отладчика (могут потребоваться дополнительные приемы) и т. д. С широкими символами, как правило, все труднее или невозможно получить все это, и вы можете получить разные части на разных платформах.
UTF-8 не предоставляет возможности использовать простые текстовые алгоритмы, которые возможны в ASCII. При этом UTF-8 ничем не хуже любой другой кодировки Unicode. Фактически, это может считаться лучшим, потому что многокодовые представления единиц в UTF-8 более распространены, и поэтому ошибки в обработке кода, такие представления символов переменной ширины, с большей вероятностью будут замечены и исправлены, чем если вы попытаетесь придерживаться UTF-32 с NFC или NFKC.
Многие платформы используют UTF-8 в качестве собственной кодировки символов, и многие программы не требуют какой-либо значительной обработки текста, поэтому написание интернационализированной программы на этих платформах мало отличается от написания кода без учета интернационализации. Написание более широко переносимого кода или на других платформах требует вставки преобразований в границы API, которые используют другие кодировки.
Другой альтернативой, используемой некоторыми программами, является выбор кроссплатформенного представления, например, коротких массивов без знака, содержащих данные UTF-16, а затем обеспечение всей поддержки библиотеки и просто расходы на поддержку языка и т. Д.
C++ 11 добавляет новые виды широких символов в качестве альтернативы wchar_t, char16_t и char32_t с сопутствующими функциями языка / библиотеки. На самом деле это не гарантированно UTF-16 и UTF-32, но я не думаю, что какая-либо крупная реализация будет использовать что-то еще. C++11 также улучшает поддержку UTF-8, например, с помощью строковых литералов UTF-8, поэтому нет необходимости обманывать VC++ для создания строк в кодировке UTF-8 (хотя я могу продолжать делать это, а не использовать u8
префикс).
Альтернативы, чтобы избежать
TCHAR: TCHAR предназначен для переноса древних программ Windows, которые принимают устаревшие кодировки с char на wchar_t, и о нем лучше всего забыть, если ваша программа не была написана в каком-то предыдущем тысячелетии. Он не является переносимым и по своей природе не специфичен в отношении кодировки и даже типа данных, что делает его непригодным для использования с любым API, не основанным на TCHAR. Поскольку его целью является переход на wchar_t, что, как мы видели выше, не очень хорошая идея, использование TCHAR вообще не имеет смысла.
1. Символы, которые представимы в строках wchar_t, но не поддерживаются ни в одной локали, не обязательно должны быть представлены одним значением wchar_t. Это означает, что wchar_t может использовать кодирование переменной ширины для определенных символов, еще одно явное нарушение намерения wchar_t. Хотя можно утверждать, что символа, представляемого с помощью wchar_t, достаточно, чтобы сказать, что языковой стандарт "поддерживает" этот символ, в этом случае кодирование с переменной шириной недопустимо, и использование UTF-16 в Window является несовместимым.
2. Юникод позволяет представлять много символов в нескольких кодовых точках, что создает те же проблемы для простых текстовых алгоритмов, что и кодирование с переменной шириной. Даже если строго придерживаться составной нормализации, некоторым символам все же требуется несколько кодовых точек. Смотрите: http://www.unicode.org/standard/where/
С wchar_t нет ничего "плохого". Проблема заключается в том, что еще в NT 3.x дни Microsoft решила, что Unicode был хорошим (так оно и есть), и реализовывать Unicode в виде 16-битных символов wchar_t. Так что большая часть литературы Microsoft середины 90-х в значительной степени приравнивается к Unicode == utf16 == wchar_t.
К сожалению, это совсем не так. "Широкие символы" не обязательно должны составлять 2 байта на всех платформах при любых обстоятельствах.
Это один из лучших учебников по Unicode (независимо от этого вопроса, независимо от C++), который я когда-либо видел: я настоятельно рекомендую это:
И я искренне верю, что лучший способ справиться с "8-битным ASCII" против "широких символов Win32" против "wchar_t-in-general" - это просто принять "Windows is Different" ... и кодировать соответственно.
ПО МОЕМУ МНЕНИЮ...
PS:
Я полностью согласен с Jamesdlin выше:
В Windows у вас нет выбора. Его внутренние API были разработаны для UCS-2, что было разумно в то время, так как это было до стандартизации кодировок UTF-8 и UTF-16 переменной длины. Но теперь, когда они поддерживают UTF-16, они оказались в худшем из обоих миров.
Обязательное чтение:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
Если вы программируете на Java или.Net (VB.Net или C#) - это, в основном, не проблема: оба по умолчанию являются Unicode. Если вы программируете в "классическом" Win32 API), вам лучше всего использовать макросы TCHAR и _T() (а не явно использовать wchar).
Я полагаю, что все компиляторы Microsoft VS2005 и более поздние версии по умолчанию для C/C++ по умолчанию 16-битные (отчасти я по-прежнему использую MSVS 6.0, когда могу;)).
Еще одно хорошее (хотя и несколько устаревшая ссылка):