C ++ 11'in Unicode'u desteklediğini okudum ve duydum. Bununla ilgili birkaç soru:
- C ++ standart kitaplığı Unicode'u ne kadar iyi destekler?
- Ne
std::string
yapmalı? - Bunu nasıl kullanabilirim?
- Potansiyel sorunlar nerede?
C ++ 11'in Unicode'u desteklediğini okudum ve duydum. Bununla ilgili birkaç soru:
std::string
yapmalı?Yanıtlar:
C ++ standart kitaplığı unicode'u ne kadar iyi destekler?
Korkunç.
Unicode desteği sağlayabilecek kütüphane olanakları arasında hızlıca tarama yapmak bana bu listeyi verir:
Bence ilk olanlar hariç hepsi korkunç bir destek sağlıyor. Diğer sorularınızı hızlıca geçtikten sonra daha ayrıntılı olarak geri döneceğim.
Ne
std::string
yapmalı?
Evet. C ++ standardına göre, bu std::string
ve kardeşlerinin yapması gerekenler:
Sınıf şablonu
basic_string
, sıranın birinci elemanı sıfır konumunda olmak üzere, değişen sayıda rasgele karakter benzeri nesneden oluşan bir sekansı depolayabilen nesneleri açıklar.
Peki, std::string
bu iyi mi. Unicode'a özgü işlevler sağlıyor mu? Hayır.
Olmalı mı? Muhtemelen değil. std::string
bir char
nesne dizisi olarak iyidir . Bu yararlı; tek sıkıntı, metnin çok düşük düzeyli bir görünümü olması ve standart C ++ daha yüksek bir düzey sağlamasıdır.
Bunu nasıl kullanabilirim?
char
Nesnelerin bir sırası olarak kullanın ; sanki acıyla bitmek zorunda olan başka bir şeymiş gibi.
Potansiyel sorunlar nerede?
Her yerde? Bakalım...
Dizeler kütüphanesi
Dizeler kütüphanesi bize sağlar basic_string
, bu sadece standardın "karakter benzeri nesneler" olarak adlandırdığı şeyin bir dizisidir. Onlara kod birimleri diyorum. Metnin üst düzey görünümünü istiyorsanız, aradığınız şey bu değildir. Bu, serileştirme / serileştirme / depolama için uygun bir metin görünümüdür.
Ayrıca, C kitaplığından dar dünya ile Unicode dünyası arasındaki boşluğu kapatmak için kullanılabilecek bazı araçlar sağlar: c16rtomb
/ mbrtoc16
ve c32rtomb
/ mbrtoc32
.
Yerelleştirme kütüphanesi
Yerelleştirme kütüphanesi hala bu "karakter benzeri nesnelerden" birinin bir "karakter" e eşit olduğuna inanmaktadır. Bu elbette aptalca ve ASCII gibi Unicode'un küçük bir alt kümesinin ötesinde düzgün bir şekilde çalışmayı mümkün kılmıyor.
Örneğin, standardın <locale>
başlıkta "kolaylık arayüzleri" olarak adlandırdığı şeyi düşünün :
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);
// ...
Bu işlevlerden herhangi birinin, örneğin U + 1F34C ʙᴀɴᴀɴᴀ 'yi u8"🍌"
veya olduğu gibi düzgün bir şekilde kategorize etmesini nasıl beklersiniz u8"\U0001F34C"
? Bu şekilde çalışmanın hiçbir yolu yoktur, çünkü bu işlevler giriş olarak yalnızca bir kod birimi alır.
Bu char32_t
yalnızca uygun bir yerel ayarla işe yarayabilir : U'\U0001F34C'
UTF-32'deki tek bir kod birimidir.
Bununla birlikte, bu hala sadece basit kasa dönüşümlerini aldığınız toupper
ve tolower
örneğin bazı Alman yerel ayarları için yeterince iyi olmadığı anlamına gelir : "ß" büyük harfleri "SS" ☦ 'ye döndürür, ancak toupper
yalnızca bir karakter kod birimi döndürebilir .
Sırada, wstring_convert
/ wbuffer_convert
ve standart kod dönüşümü yönü.
wstring_convert
bir kodlamadaki dizeler arasında başka bir kodlamadaki dizelere dönüştürmek için kullanılır. Bu dönüşümde, standartın bayt dizesini ve geniş bir dizeyi çağırdığı iki dize türü vardır. Bu terimler gerçekten yanıltıcı olduğundan, † yerine "seri hale getirilmiş" ve "serileştirilmiş" kullanmayı tercih ederim.
Bir codecvt (bir kod çevrim faset) tarafından karar verilir arasında dönüştürmek için kodlamalar için şablon tipi bağımsız değişken olarak geçirilen wstring_convert
.
wbuffer_convert
benzer bir işlevi yerine getirir, ancak bir bayt serileştirilmiş akış arabelleğini saran geniş bir serileştirilmiş olmayan akış tamponu olarak çalışır. Herhangi bir G / Ç, kodlayıcı argümanı tarafından verilen kodlamalara ve kodlamalardan dönüştürmelerle temel alınan bayt serileştirilmiş akış tamponu aracılığıyla gerçekleştirilir . Yazma bu ara belleğe serileştirir ve ondan yazar ve okuma ara belleğe okur ve sonra aradan serisini kaldırır.
Standart Bu tesislerde kullanılmak üzere bazı codecvt sınıfı şablonlar sağlar: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
ve bazı codecvt
uzmanlık. Bu standart özellikler birlikte aşağıdaki dönüşümlerin tümünü sağlar. (Not: aşağıdaki listede, soldaki kodlama her zaman serileştirilmiş dize / streambuf'tur ve sağdaki kodlama her zaman deserialized dize / streambuf'tur; standart her iki yönde de dönüşümlere izin verir).
codecvt_utf8<char16_t>
ve codecvt_utf8<wchar_t>
nerede sizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
ve ;codecvt_utf8<wchar_t>
sizeof(wchar_t) == 4
codecvt_utf16<char16_t>
ve codecvt_utf16<wchar_t>
nerede sizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
ve codecvt_utf16<wchar_t>
nerede sizeof(wchar_t) == 4
;codecvt_utf8_utf16<char16_t>
, codecvt<char16_t, char, mbstate_t>
ve ;codecvt_utf8_utf16<wchar_t>
sizeof(wchar_t) == 2
codecvt<wchar_t, char_t, mbstate_t>
codecvt<char, char, mbstate_t>
.Bunların birçoğu faydalıdır, ancak burada bir sürü garip şey var.
İlk önce — kutsal yüksek vekil! bu adlandırma şeması dağınık.
Sonra bir sürü UCS-2 desteği var. UCS-2, 1996'da yerini alan Unicode 1.0'ın yalnızca temel çok dilli düzlemi desteklediği için bir kodlamadır. Komite neden 20 yıl önce yerini alan bir kodlamaya odaklanmak istediğini düşünüyor, bilmiyorum ‡. Daha fazla kodlama için destek kötü ya da herhangi bir şey gibi değil, ancak UCS-2 burada çok sık ortaya çıkıyor.
char16_t
Açıkçası UTF-16 kod birimlerini saklamak için olduğunu söyleyebilirim . Ancak bu, standardın aksini düşünen bir parçasıdır. codecvt_utf8<char16_t>
UTF-16 ile ilgisi yoktur. Örneğin, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
iyi derlenecek, ancak koşulsuz olarak başarısız olacaktır: girdi UCS-2 dizesi olarak ele u"\xD83C\xDF4C"
alınacaktır; UTF-8, 0xD800-0xDFFF aralığındaki herhangi bir değeri kodlayamadığı için UTF-8'e dönüştürülemez.
Yine de UCS-2 cephesinde, UTF-16 bayt akışından bu yönlerle UTF-16 dizesine okumak mümkün değildir. UTF-16 baytlık bir diziniz varsa dizginin serisini kaldıramazsınız char16_t
. Bu şaşırtıcıdır, çünkü aşağı yukarı bir kimlik dönüşümüdür. Bununla birlikte, daha da şaşırtıcı olan, UTF-16 akışından bir UCS-2 dizesine serileştirme için destek olması gerçeğidir codecvt_utf16<char16_t>
, bu aslında kayıplı bir dönüşümdür.
Bununla birlikte, bayt olarak UTF-16 desteği oldukça iyidir: bir Malzeme Listesindeki endianitenin algılanmasını veya kodda açıkça seçilmesini destekler. Ayrıca ürün ağacıyla veya ürün ağacı olmadan çıktı üretmeyi de destekler.
Daha ilginç dönüşüm olanakları yok. UTF-8 bayt akışından veya dizesinden UTF-8 dizesine serileştirmenin bir yolu yoktur, çünkü UTF-8 hiçbir zaman serileştirilmiş form olarak desteklenmez.
Ve burada dar / geniş dünya UTF / UCS dünyasından tamamen ayrı. Eski stil dar / geniş kodlamalar ile herhangi bir Unicode kodlamalar arasında dönüşüm yoktur.
Giriş / çıkış kütüphanesi
G / Ç kütüphanesi okumak ve Unicode kodlama yazma metni kullanarak için kullanılabilir wstring_convert
ve wbuffer_convert
tesisler yukarıda anlatılan. Standart kütüphanenin bu kısmı tarafından desteklenmesi gereken çok fazla şey olduğunu sanmıyorum.
Normal ifadeler kütüphanesi
Daha önce Stack Overflow C ++ regexes ve Unicode ile ilgili sorunlar üzerine açıkladı . Burada tüm bu noktaları tekrarlamak istemiyorum, ama sadece C ++ regexes her yerde UTF-32 kullanmaya başvurmadan kullanılabilir hale getirmek için minimum minimum seviye 1 Unicode desteği olmadığını belirtmek.
Bu kadar?
Evet, bu o. Mevcut işlevsellik budur. Normalleştirme veya metin segmentasyon algoritmaları gibi hiçbir yerde görülmeyen çok sayıda Unicode işlevi var.
U + 1F4A9 . C ++ ile daha iyi Unicode desteği almanın herhangi bir yolu var mı?
Olağan şüpheliler: ICU ve Boost.Locale .
† Bir bayt dizesi, şaşırtıcı olmayan bir şekilde, bir bayt dizisidir, yani char
nesnelerdir. Ancak, her zaman bir nesne dizisi olan geniş bir dize değişmezinin aksine wchar_t
, bu bağlamdaki bir "geniş dize" mutlaka bir wchar_t
nesne dizesi değildir . Aslında, standart asla bir "geniş dize" nin ne anlama geldiğini açıkça tanımlamaz, bu yüzden kullanımdan anlamını tahmin etmeye bırakılırız. Standart terminoloji özensiz ve kafa karıştırıcı olduğu için, açıklık adına kendiminkini kullanıyorum.
UTF-16 gibi kodlamalar char16_t
, daha sonra endianitesi olmayan diziler halinde saklanabilir ; veya endianiteye sahip bayt dizileri olarak saklanabilirler (ardışık her bayt çifti char16_t
endianiteye bağlı olarak farklı bir değeri temsil edebilir ). Standart bu formların her ikisini de destekler. Bir dizi char16_t
, programdaki dahili manipülasyon için daha kullanışlıdır. Bir bayt dizisi, bu dizeleri dış dünyayla değiştirmenin yoludur. "Bayt" ve "geniş" yerine kullanacağım terimler "serileştirilmiş" ve "serileştirilmiş" olur.
"" Ama Windows! "Demek üzereyseniz tutun 🐎🐎 . Windows 2000'den bu yana tüm Windows sürümleri UTF-16 kullanır.
☦ Evet, ben Eszett großes (ẞ) biliyorum , ama bir gecede ß büyük harf have için tüm Alman yerel değiştirmek için bile, bu başarısız olacağı birçok durumda hala var. Büyük U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ʟɪɢᴀᴛᴜʀᴇ yazmayı deneyin. ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ yoktur; sadece iki F'ye karşılık gelir. Veya U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; önceden oluşturulmuş sermaye yoktur; sadece bir başkent J ve kombine bir caron'un üstünü kaplar.
Unicode, Standart Kütüphane tarafından desteklenmez (desteklenen herhangi bir makul anlam için).
std::string
std::vector<char>
Şundan daha iyi değildir : Unicode (veya başka herhangi bir gösterim / kodlama) için tamamen habersizdir ve içeriğine bayt bloğu olarak davranır .
Sadece lekeleri saklamanız ve katetmeniz gerekiyorsa , oldukça iyi çalışır; ancak Unicode işlevselliği ( kod noktası sayısı, grafik sayısı vb.) için istediğiniz anda şansınız kalmaz .
Bunun için bildiğim tek kapsamlı kütüphane ICU . C ++ arayüzü Java'dan türetildi, bu yüzden deyimsel olmaktan çok uzak.
Güvenle bir UTF-8 saklayabilir std::string
(veya bir char[]
ya char*
, bu konuda) nedeniyle Unicode NUL (+ 0000 U) UTF-8 boş bayt olması ve bu tek yönlü boş olduğunu bayt UTF-8'de meydana gelebilir. Bu nedenle, UTF-8 dizeleriniz tüm C ve C ++ dize işlevlerine göre uygun şekilde sonlandırılacak ve C ++ iostreams ( std::cout
ve std::cerr
yerel ayarınız UTF-8 olduğu sürece) etrafında onları askıya alabilirsiniz .
std::string
UTF-8 için yapamayacağınız şey kod noktalarında uzunluk almaktır. std::string::size()
dize uzunluğunu bayt cinsinden söyleyecektir , bu yalnızca UTF-8'in ASCII alt kümesinde olduğunuzda kod noktalarının sayısına eşittir.
Kod noktası düzeyinde UTF-8 dizeleri üzerinde çalışmanız gerekiyorsa (yani yalnızca depolayıp yazdırmakla kalmaz) veya birçok dahili boş bayta sahip olması muhtemel UTF-16 ile uğraşıyorsanız, geniş karakter dizesi türleri.
std::string
gömülü boş değerlerle iyice akabilir.
c_str()
çünkü size()
hala çalışır. Yalnızca bozuk API'lar (yani, C dünyasının çoğu gibi gömülü null'ları işleyemeyenler) bozulur.
c_str()
çünkü c_str()
verileri boş sonlandırılmış bir C dizesi olarak döndürmesi beklenir - bu, C dizelerinin katıştırılmış boş değerlere sahip olmaması nedeniyle imkansızdır.
c_str()
şimdi sadece aynı şekilde data()
, yani hepsi ile döner . Boyut alan API'ler onu tüketebilir. Yapmayan, yapamayan API'lar.
c_str()
Sonucun NUL karakter benzeri bir nesne tarafından takip edilmesini sağlayan küçük bir farkla , ve sanmıyorum data()
. Hayır, data()
şimdi de öyle görünüyor . (Tabii ki, bu, sonlandırıcı aramasından çıkarım yapmak yerine boyutu tüketen API'ler için gerekli değildir)
C ++ 11, Unicode için birkaç yeni değişmez dize türüne sahiptir .
Maalesef standart olmayan kodlamaların (UTF-8 gibi) standart kütüphanedeki desteği hala kötüdür. Örneğin, UTF-8 dizesinin uzunluğunu (kod noktalarında) almanın iyi bir yolu yoktur.
std::string
, UTF-8 dizesini sorunsuz bir şekilde tutabilir , ancak örneğin length
yöntem, kod noktası sayısını değil dizedeki bayt sayısını döndürür.
ñ
'TILDE İLE LATİN KÜÇÜK MEKTUP N' (U + 00F1) (bir kod noktasıdır) veya 'LATIN KÜÇÜK MEKTUP N' ( U + 006E) ve ardından iki kod noktası olan 'TILDE COMBINING' (U + 0303).
LATIN SMALL LETTER N'
== dikkate alıp almamaya karar vermesi ayrıştırıcının belirtimine bağlıdır (U+006E) followed by 'COMBINING TILDE' (U+0303)
.
Ancak denilen oldukça kullanışlı bir kütüphane de bulunmaktadır minik-utf8 temelde olduğunu, açılan yerine ilişkin std::string
/ ' std::wstring
. Hala eksik olan utf8-string konteyner sınıfının boşluğunu doldurmayı amaçlıyor.
Bu, utf8 dizeleriyle (yani unicode normalleştirme ve benzeri şeyler olmadan) en rahat yol olabilir. Dizeniz çalışma uzunluğu kodlu kodlarda kodlanmış halde kalırken kod noktalarında rahatça çalışabilirsiniz char
.