Что «не так» с C ++ wchar_t и wstrings? Какие есть альтернативы широким символам?

86

Я видел, как много людей в сообществе C ++ (особенно ## c ++ на freenode) возражали против использования wstringsи wchar_tи их использования в API Windows. Что на самом деле «не так» с wchar_tи wstring, и если я хочу поддержать интернационализацию, каковы альтернативы широким символам?

Кен Ли
источник
1
Есть ссылки на это?
Дэни
14
Возможно, эта классная ветка ответит на все ваши вопросы? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox
15
В Windows у вас действительно нет выбора. Его внутренние API-интерфейсы были разработаны для UCS-2, что было разумным в то время, поскольку это было до того, как кодировки переменной длины UTF-8 и UTF-16 были стандартизированы. Но теперь, когда они поддерживают UTF-16, они получили худшее из обоих миров.
jamesdlin
12
На utf8everywhere.org есть хорошее обсуждение причин избегать широких символов.
JoeG
5
@jamesdlin Конечно, у тебя есть выбор. Библиотека nowide предоставляет удобный способ конвертировать строки прямо при переходе к API. Вызовы API со строками обычно низкочастотны, поэтому разумным способом будет преобразование ad-hok и постоянное использование файлов и внутренних переменных в UTF-8.
Павел Радзивиловский

Ответы:

114

Что такое wchar_t?

wchar_t определен таким образом, что любая кодировка символов языкового стандарта может быть преобразована в представление 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, достаточно, чтобы сказать, что локаль «поддерживает» этот символ, и в этом случае кодировки переменной ширины недопустимы, а использование Window UTF-16 не соответствует требованиям.

2. Unicode позволяет представлять множество символов с помощью нескольких кодовых точек, что создает те же проблемы для простых текстовых алгоритмов, что и кодирование переменной ширины. Даже если строго соблюдается составная нормализация, некоторые символы все равно требуют нескольких кодовых точек. См .: http://www.unicode.org/standard/where/

bames53
источник
3
Дополнение: utf8everywhere.org рекомендует использовать UTF-8 в Windows, а Boost.Nowide запланирован на официальное рассмотрение.
Яков Галка
2
Лучше всего, конечно, использовать C # или VB.Net в Windows :) Или старый добрый C / Win32. Но если вы должны использовать C ++, то TCHAR - лучший способ. По умолчанию это "wchar_t" в MSVS2005 и выше. ИМХО ...
paulsm4
4
@BrendanMcK: Конечно, кода, использующего Win32 API в Windows и других API в других системах, не существует. Правильно? Проблема подхода Microsoft («используйте wchar внутри везде в вашем приложении») заключается в том, что он влияет даже на код, который не взаимодействует с системой напрямую и может быть переносимым.
Яков Галка
4
Проблема в том, что вам нужно использовать специфические для Windows функции, потому что решение Microsoft не поддерживать UTF-8 в качестве кодовой страницы ANSI «ломает» стандартную библиотеку C (++). Например, вы не можете fopenфайл, имя которого содержит символы, отличные от ANSI.
dan04
11
@ dan04 Да, вы не можете использовать стандартную библиотеку в Windows, но вы можете создать переносимый интерфейс, который объединяет стандартную библиотеку на других платформах и преобразует UTF-8 в wchar_t непосредственно перед использованием функций Win32 W.
bames53
20

В 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 отличается» ... и код соответственно.

ПО МОЕМУ МНЕНИЮ...

PS:

Я полностью согласен с приведенным выше jamesdlin:

В Windows у вас действительно нет выбора. Его внутренние API-интерфейсы были разработаны для UCS-2, что было разумным в то время, поскольку это было до того, как кодировки переменной длины UTF-8 и UTF-16 были стандартизированы. Но теперь, когда они поддерживают UTF-16, они получили худшее из обоих миров.

paulsm4
источник