Co jest „nie tak” z C ++ wchar_t i wstrings? Jakie są alternatywy dla szerokich znaków?

86

Widziałem wiele osób w społeczności C ++ (szczególnie ## c ++ na freenode) niechętnie używają wstringsi wchar_toraz ich używania w api systemu Windows. Co jest dokładnie „nie tak” w przypadku wchar_ti wstringi jeśli chcę wspierać internacjonalizację, jakie są alternatywy dla szerokich znaków?

Ken Li
źródło
1
Masz na to jakieś referencje?
Dani
14
Może ten wspaniały wątek odpowie na wszystkie Twoje pytania? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox
15
W systemie Windows tak naprawdę nie masz wyboru. Jego wewnętrzne interfejsy API zostały zaprojektowane dla UCS-2, co było rozsądne w tamtym czasie, ponieważ było przed standaryzacją kodowań UTF-8 i UTF-16 o zmiennej długości. Ale teraz, gdy obsługują UTF-16, skończyli na najgorszym z obu światów.
jamesdlin
12
utf8everywhere.org zawiera dobre omówienie powodów, dla których należy unikać szerokich znaków.
JoeG
5
@jamesdlin Z pewnością masz wybór. Biblioteka nowide zapewnia wygodny sposób konwertowania ciągów znaków tylko podczas przekazywania ich do interfejsów API. Wywołania API z ciągami są zwykle niskoczęstotliwościowe, więc rozsądnym sposobem jest konwersja ad-hok i ciągłe przechowywanie plików i zmiennych wewnętrznych w UTF-8.
Pavel Radzivilovsky

Odpowiedzi:

114

Co to jest wchar_t?

wchar_t jest zdefiniowany w taki sposób, że kodowanie znaków dowolnego ustawienia regionalnego można przekonwertować na reprezentację wchar_t, gdzie każdy wchar_t reprezentuje dokładnie jeden punkt kodowy:

Typ wchar_t jest typem odrębnym, którego wartości mogą reprezentować różne kody dla wszystkich elementów członkowskich największego rozszerzonego zestawu znaków określonego spośród obsługiwanych ustawień regionalnych (22.3.1).

                                                                               - C ++ [basic.fundamental] 3.9.1 / 5

To nie wymagają wchar_t być wystarczająco duże, aby reprezentować dowolny znak ze wszystkich lokalizacjach jednocześnie. Oznacza to, że kodowanie używane dla wchar_t może się różnić w zależności od lokalizacji. Oznacza to, że niekoniecznie można przekonwertować ciąg na wchar_t przy użyciu jednego ustawienia narodowego, a następnie przekonwertować go z powrotem na znak przy użyciu innego ustawienia narodowego. 1

Ponieważ użycie wchar_t jako wspólnej reprezentacji między wszystkimi lokalizacjami wydaje się być podstawowym zastosowaniem wchar_t w praktyce, możesz się zastanawiać, do czego jest dobry, jeśli nie do tego.

Pierwotnym zamiarem i celem wchar_t było uproszczenie przetwarzania tekstu poprzez zdefiniowanie go tak, że wymaga odwzorowania jeden do jednego z jednostek kodu ciągu na znaki tekstu, umożliwiając w ten sposób użycie tych samych prostych algorytmów, które są używane z ciągami ascii do pracy z innymi językami.

Niestety, sformułowanie specyfikacji wchar_t zakłada odwzorowanie jeden do jednego między znakami i punktami kodowymi, aby to osiągnąć. Unicode łamie to założenie 2 , więc nie można bezpiecznie używać wchar_t również dla prostych algorytmów tekstowych.

Oznacza to, że oprogramowanie przenośne nie może używać wchar_t ani jako wspólnej reprezentacji tekstu między lokalizacjami, ani w celu umożliwienia użycia prostych algorytmów tekstowych.

Jakie zastosowanie ma dzisiaj wchar_t?

W każdym razie niewiele, jak na przenośny kod. Jeśli __STDC_ISO_10646__jest zdefiniowane, to wartości wchar_t bezpośrednio reprezentują punkty kodowe Unicode z tymi samymi wartościami we wszystkich lokalizacjach. To sprawia, że ​​konwersje międzylokalne, o których mowa wcześniej, są bezpieczne. Jednak nie możesz polegać tylko na nim, aby zdecydować, że możesz użyć wchar_t w ten sposób, ponieważ podczas gdy większość platform uniksowych definiuje to, Windows nie używa tego samego ustawienia narodowego wchar_t we wszystkich lokalizacjach.

Powodem, dla którego system Windows nie definiuje, __STDC_ISO_10646__jest to, że system Windows używa kodowania UTF-16 jako swojego kodowania wchar_t, a ponieważ UTF-16 używa par zastępczych do reprezentowania punktów kodowych większych niż U + FFFF, co oznacza, że ​​UTF-16 nie spełnia wymagań dla __STDC_ISO_10646__.

W przypadku kodu specyficznego dla platformy wchar_t może być bardziej przydatny. Zasadniczo jest to wymagane w systemie Windows (np. Niektóre pliki po prostu nie mogą być otwierane bez użycia nazw plików wchar_t), chociaż Windows jest jedyną platformą, na której jest to prawdą, o ile wiem (więc może możemy myśleć o wchar_t jako o „Windows_char_t”).

Z perspektywy czasu wchar_t najwyraźniej nie jest przydatny do upraszczania obsługi tekstu lub do przechowywania tekstu niezależnego od ustawień regionalnych. Kod przenośny nie powinien próbować używać go do tych celów. Kod nieprzenośny może okazać się przydatny po prostu dlatego, że wymaga tego niektóre API.

Alternatywy

Alternatywą, którą lubię, jest użycie ciągów C zakodowanych w UTF-8, nawet na platformach niezbyt przyjaznych dla UTF-8.

W ten sposób można napisać przenośny kod przy użyciu wspólnej reprezentacji tekstowej na różnych platformach, użyć standardowych typów danych zgodnie z ich przeznaczeniem, uzyskać obsługę języka dla tych typów (np. obsługa standardowej biblioteki, obsługa debuggera (może być potrzebnych więcej trików), itp. W przypadku szerokich znaków uzyskanie tego wszystkiego jest zazwyczaj trudniejsze lub niemożliwe i możesz otrzymać różne elementy na różnych platformach.

Jedną rzeczą, której UTF-8 nie zapewnia, jest możliwość korzystania z prostych algorytmów tekstowych, jakie są możliwe w ASCII. W tym UTF-8 nie jest gorszy niż jakiekolwiek inne kodowanie Unicode. W rzeczywistości można to uznać za lepsze, ponieważ reprezentacje jednostek wielokodowych w UTF-8 są bardziej powszechne, a więc błędy w kodzie obsługującym takie reprezentacje o zmiennej szerokości znaków są bardziej prawdopodobne, że zostaną zauważone i naprawione, niż gdybyś próbował trzymać się UTF -32 z NFC lub NFKC.

Wiele platform używa UTF-8 jako swojego natywnego kodowania znaków, a wiele programów nie wymaga żadnego znaczącego przetwarzania tekstu, więc pisanie umiędzynarodowionego programu na tych platformach niewiele różni się od pisania kodu bez uwzględnienia internacjonalizacji. Pisanie szerzej przenośnego kodu lub pisanie na innych platformach wymaga wstawiania konwersji na granicach interfejsów API korzystających z innych kodowań.

Inną alternatywą używaną przez niektóre programy jest wybranie reprezentacji międzyplatformowej, takiej jak krótkie tablice bez znaku przechowujące dane UTF-16, a następnie dostarczenie całej obsługi bibliotek i po prostu życie z kosztami obsługi języka itp.

C ++ 11 dodaje nowe rodzaje szerokich znaków jako alternatywę dla wchar_t, char16_t i char32_t z towarzyszącymi funkcjami języka / biblioteki. W rzeczywistości nie ma gwarancji, że będą to UTF-16 i UTF-32, ale nie wyobrażam sobie, aby jakakolwiek większa implementacja używała niczego innego. C ++ 11 poprawia również obsługę UTF-8, na przykład z literałami łańcuchowymi UTF-8, więc nie będzie konieczne oszukiwanie VC ++ do tworzenia zakodowanych ciągów UTF-8 (chociaż mogę nadal to robić, zamiast używać u8prefiksu) .

Alternatywy, których należy unikać

TCHAR: TCHAR służy do migracji starych programów Windows, które zakładają starsze kodowanie z char do wchar_t i najlepiej o nim zapomnieć, chyba że twój program został napisany w jakimś poprzednim tysiącleciu. Nie jest przenośny i jest z natury niespecyficzny co do jego kodowania, a nawet typu danych, co czyni go bezużytecznym z żadnym interfejsem API innym niż TCHAR. Ponieważ jego celem jest migracja do wchar_t, co widzieliśmy powyżej, nie jest dobrym pomysłem, używanie TCHAR nie ma żadnej wartości.


1. Znaki, które są reprezentowane w łańcuchach wchar_t, ale które nie są obsługiwane w żadnym ustawieniu narodowym, nie muszą być reprezentowane przez pojedynczą wartość wchar_t. Oznacza to, że wchar_t może używać kodowania o zmiennej szerokości dla niektórych znaków, co jest kolejnym wyraźnym naruszeniem intencji wchar_t. Chociaż można spierać się, że znak, który jest reprezentowany przez wchar_t, wystarczy, aby powiedzieć, że ustawienia narodowe „obsługują” ten znak, w którym to przypadku kodowanie o zmiennej szerokości nie jest dozwolone, a użycie UTF-16 w systemie Windows jest niezgodne.

2. Unicode umożliwia przedstawienie wielu znaków w wielu punktach kodowych, co stwarza te same problemy w przypadku prostych algorytmów tekstowych, co w przypadku kodowania o zmiennej szerokości. Nawet jeśli ściśle przestrzega się złożonej normalizacji, niektóre znaki nadal wymagają wielu punktów kodowych. Zobacz: http://www.unicode.org/standard/where/

bames53
źródło
3
Dodatek: utf8everywhere.org zaleca używanie UTF-8 w systemie Windows, a Boost.Nowide ma zostać poddane formalnej weryfikacji.
Yakov Galka
2
Najlepszą rzeczą jest oczywiście użycie C # lub VB.Net na Windowsie :) Lub zwykłego starego C / Win32. Ale jeśli musisz używać C ++, wtedy TCHAR jest najlepszym rozwiązaniem. Który domyślnie to „wchar_t” w MSVS2005 i nowszych. IMHO ...
paulsm4
4
@BrendanMcK: Jasne, kod korzystający z interfejsu API Win32 w systemie Windows i innych interfejsów API w innych systemach nie istnieje. Dobrze? Problem z podejściem firmy Microsoft („użyj elementów wewnątrz aplikacji wszędzie w aplikacji”) polega na tym, że dotyczy to nawet kodu, który nie łączy się bezpośrednio z systemem i może być przenośny.
Yakov Galka
4
Problemem jest to, że muszą korzystać z funkcji specyficznych dla systemu Windows z powodu decyzji Microsoft nie obsługuje UTF-8 jako strona kodowa ANSI „przerw” Standard C (++) biblioteka. Na przykład nie możesz fopenplik, którego nazwa zawiera znaki inne niż ANSI.
dan04,
11
@ dan04 Tak, nie możesz używać standardowej biblioteki w systemie Windows, ale możesz stworzyć przenośny interfejs, który opakuje standardową bibliotekę na innych platformach i konwertuje z UTF-8 do wchar_t bezpośrednio przed użyciem funkcji Win32 W.
bames53
20

Nie ma nic "złego" w wchar_t. Problem polega na tym, że w NT 3.x dni Microsoft zdecydował, że Unicode jest dobry (jest) i zaimplementował Unicode jako 16-bitowe znaki wchar_t. Tak więc większość literatury firmy Microsoft z połowy lat 90-tych prawie zrównuje Unicode == utf16 == wchar_t.

Co niestety wcale nie jest prawdą. „Szerokie znaki” niekoniecznie muszą mieć 2 bajty, na wszystkich platformach, w każdych okolicznościach.

To jeden z najlepszych podkładów na temat „Unicode” (niezależny od tego pytania, niezależny od C ++), jaki kiedykolwiek widziałem: bardzo go polecam:

I szczerze wierzę, że najlepszym sposobem radzenia sobie z „8-bitowym ASCII” w porównaniu z „szerokimi znakami Win32” w porównaniu z „wchar_t-in-general” jest po prostu zaakceptowanie, że „Windows jest inny”… i odpowiednio zakodować.

MOIM ZDANIEM...

PS:

Całkowicie zgadzam się z powyższym jamesdlinem:

W systemie Windows tak naprawdę nie masz wyboru. Jego wewnętrzne interfejsy API zostały zaprojektowane dla UCS-2, co było rozsądne w tamtym czasie, ponieważ było przed standaryzacją kodowań UTF-8 i UTF-16 o zmiennej długości. Ale teraz, gdy obsługują UTF-16, skończyli na najgorszym z obu światów.

paulsm4
źródło