Saya telah membaca dan mendengar bahwa C ++ 11 mendukung Unicode. Beberapa pertanyaan tentang itu:
- Seberapa baik pustaka standar C ++ mendukung Unicode?
- Apakah
std::string
melakukan apa yang seharusnya? - Bagaimana saya menggunakannya?
- Di mana ada potensi masalah?
Jawaban:
Sangat.
Pemindaian cepat melalui fasilitas perpustakaan yang mungkin menyediakan dukungan Unicode memberi saya daftar ini:
Saya pikir semua kecuali yang pertama memberikan dukungan yang mengerikan. Saya akan kembali ke hal itu dengan lebih detail setelah memutar cepat melalui pertanyaan Anda yang lain.
Iya. Menurut standar C ++, inilah yang
std::string
harus dilakukan dan saudara-saudaranya:Nah,
std::string
apakah itu baik-baik saja. Apakah itu menyediakan fungsionalitas khusus Unicode? Tidak.Haruskah itu Mungkin tidak.
std::string
baik-baik saja sebagai urutanchar
objek. Itu berguna; satu-satunya gangguan adalah bahwa itu adalah tampilan yang sangat rendah dari teks dan standar C ++ tidak memberikan yang lebih tinggi.Gunakan sebagai urutan
char
objek; berpura-pura itu adalah sesuatu yang lain pasti berakhir dengan rasa sakit.Seluruh tempat? Ayo lihat...
Perpustakaan string
Perpustakaan string memberi kita
basic_string
, yang hanya merupakan urutan dari apa yang disebut standar "objek seperti char". Saya menyebutnya unit kode. Jika Anda menginginkan tampilan teks tingkat tinggi, ini bukan yang Anda cari. Ini adalah tampilan teks yang cocok untuk serialisasi / deserialisasi / penyimpanan.Ini juga menyediakan beberapa alat dari pustaka C yang dapat digunakan untuk menjembatani kesenjangan antara dunia sempit dan dunia Unicode:
c16rtomb
/mbrtoc16
danc32rtomb
/mbrtoc32
.Perpustakaan lokalisasi
Perpustakaan lokalisasi masih percaya bahwa salah satu dari "objek char-like" sama dengan satu "karakter". Ini tentu saja konyol, dan membuatnya mustahil untuk mendapatkan banyak hal berfungsi dengan baik di luar beberapa subset kecil Unicode seperti ASCII.
Pertimbangkan, misalnya, apa yang panggilan standar "antarmuka kenyamanan" di
<locale>
header:Bagaimana Anda mengharapkan salah satu dari fungsi-fungsi ini untuk mengkategorikan dengan benar, misalnya, U + 1F34C ʙᴀɴᴀɴᴀ, seperti pada
u8"🍌"
atauu8"\U0001F34C"
? Tidak mungkin itu akan berhasil, karena fungsi-fungsi itu hanya mengambil satu unit kode sebagai input.Ini bisa berfungsi dengan lokal yang sesuai jika Anda
char32_t
hanya menggunakan :U'\U0001F34C'
adalah unit kode tunggal di UTF-32.Namun, itu tetap berarti Anda hanya mendapatkan transformasi casing sederhana dengan
toupper
dantolower
, yang, misalnya, tidak cukup baik untuk beberapa lokal Jerman: "ß" huruf besar menjadi "SS" ☦ tetapitoupper
hanya dapat mengembalikan satukarakterunit kode .Selanjutnya,
wstring_convert
/wbuffer_convert
dan aspek konversi kode standar.wstring_convert
digunakan untuk mengkonversi antara string dalam satu pengkodean yang diberikan menjadi string dalam pengkodean lain yang diberikan. Ada dua tipe string yang terlibat dalam transformasi ini, yang standar memanggil string byte dan string lebar. Karena istilah-istilah ini benar-benar menyesatkan, saya masing-masing lebih suka menggunakan "serial" dan "deserialized", †.Pengkodean untuk mengkonversi antara diputuskan oleh codecvt (segi konversi kode) diteruskan sebagai argumen tipe templat ke
wstring_convert
.wbuffer_convert
melakukan fungsi yang serupa tetapi sebagai buffer aliran deserializedlebaryang membungkusbytestream buffer serial. Setiap I / O dilakukan melalui buffer aliran serialbyte yangmendasari dengan konversi ke dan dari pengkodean yang diberikan oleh argumen codecvt. Menulis serialisasi ke buffer itu, dan kemudian menulis darinya, dan membaca membaca ke buffer dan kemudian deserializes dari itu.Standar menyediakan beberapa template kelas codecvt untuk digunakan dengan fasilitas ini:
codecvt_utf8
,codecvt_utf16
,codecvt_utf8_utf16
, dan beberapacodecvt
spesialisasi. Bersama-sama, aspek standar ini menyediakan semua konversi berikut. (Catatan: dalam daftar berikut, pengkodean di sebelah kiri selalu berupa string berseri / streambuf, dan pengodean di sebelah kanan selalu berupa string / streambuf deserialisasi; standar memungkinkan konversi di kedua arah).codecvt_utf8<char16_t>
, dan dicodecvt_utf8<wchar_t>
manasizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
,codecvt<char32_t, char, mbstate_t>
, dancodecvt_utf8<wchar_t>
di manasizeof(wchar_t) == 4
;codecvt_utf16<char16_t>
, dan dicodecvt_utf16<wchar_t>
manasizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
, dan dicodecvt_utf16<wchar_t>
manasizeof(wchar_t) == 4
;codecvt_utf8_utf16<char16_t>
,codecvt<char16_t, char, mbstate_t>
, dancodecvt_utf8_utf16<wchar_t>
di manasizeof(wchar_t) == 2
;codecvt<wchar_t, char_t, mbstate_t>
codecvt<char, char, mbstate_t>
.Beberapa di antaranya bermanfaat, tetapi ada banyak hal aneh di sini.
Yang pertama — ibu pengganti yang suci! skema penamaan itu berantakan.
Lalu, ada banyak dukungan UCS-2. UCS-2 adalah penyandian dari Unicode 1.0 yang digantikan pada tahun 1996 karena hanya mendukung bidang multibahasa dasar. Mengapa panitia berpikir ingin fokus pada pengkodean yang digantikan lebih dari 20 tahun yang lalu, saya tidak tahu ‡. Ini tidak seperti dukungan untuk lebih banyak pengkodean buruk atau apa pun, tetapi UCS-2 muncul terlalu sering di sini.
Saya akan mengatakan bahwa
char16_t
ini jelas dimaksudkan untuk menyimpan unit kode UTF-16. Namun, ini adalah salah satu bagian dari standar yang berpikir sebaliknya.codecvt_utf8<char16_t>
tidak ada hubungannya dengan UTF-16. Sebagai contoh,wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
akan dikompilasi dengan baik, tetapi akan gagal tanpa syarat: input akan diperlakukan sebagai string UCS-2u"\xD83C\xDF4C"
, yang tidak dapat dikonversi ke UTF-8 karena UTF-8 tidak dapat menyandikan nilai apa pun dalam kisaran 0xD800-0xDFFF.Masih di depan UCS-2, tidak ada cara untuk membaca dari aliran UTF-16 byte menjadi string UTF-16 dengan sisi-sisi ini. Jika Anda memiliki urutan UTF-16 byte, Anda tidak dapat membatalkan deserialize menjadi string
char16_t
. Ini mengejutkan, karena ini lebih atau kurang merupakan konversi identitas. Yang lebih mengejutkan adalah kenyataan bahwa ada dukungan untuk deserialisasi dari aliran UTF-16 ke dalam string UCS-2 dengancodecvt_utf16<char16_t>
, yang sebenarnya merupakan konversi yang hilang.Dukungan UTF-16-as-bytes cukup bagus, meskipun: mendukung mendeteksi endianess dari BOM, atau memilihnya secara eksplisit dalam kode. Ini juga mendukung menghasilkan output dengan dan tanpa BOM.
Ada beberapa kemungkinan konversi yang lebih menarik. Tidak ada cara deserialize dari aliran UTF-16 byte atau string ke string UTF-8, karena UTF-8 tidak pernah didukung sebagai bentuk deserialized.
Dan di sini dunia sempit / lebar benar-benar terpisah dari dunia UTF / UCS. Tidak ada konversi antara pengkodean sempit / lebar gaya lama dan pengkodean Unicode apa pun.
Pustaka input / output
Perpustakaan I / O dapat digunakan untuk membaca dan menulis teks dalam pengkodean Unicode menggunakan
wstring_convert
danwbuffer_convert
fasilitas yang dijelaskan di atas. Saya tidak berpikir ada banyak hal lain yang perlu didukung oleh bagian dari perpustakaan standar ini.Pustaka ekspresi reguler
Saya telah menjelaskan masalah dengan C ++ regexes dan Unicode di Stack Overflow sebelumnya. Saya tidak akan mengulangi semua poin tersebut di sini, tetapi hanya menyatakan bahwa C ++ regex tidak memiliki dukungan Unicode level 1, yang merupakan jumlah minimum untuk membuatnya dapat digunakan tanpa menggunakan UTF-32 di mana-mana.
Ya itu saja. Itulah fungsionalitas yang ada. Ada banyak fungsi Unicode yang tidak terlihat seperti normalisasi atau algoritma segmentasi teks.
Tersangka yang biasa: ICU dan Boost.Locale .
String String byte adalah, tidak mengherankan, string byte, yaitu
char
objek. Namun, tidak seperti string string literal , yang selalu merupakan arraywchar_t
objek, "string lebar" dalam konteks ini tidak harus berupa stringwchar_t
objek. Faktanya, standar tidak pernah secara eksplisit mendefinisikan apa arti "string lebar", jadi kita tinggal menebak arti dari penggunaan. Karena terminologi standarnya ceroboh dan membingungkan, saya menggunakan istilah saya sendiri, atas nama kejelasan.Pengkodean seperti UTF-16 dapat disimpan sebagai urutan
char16_t
, yang kemudian tidak memiliki endianness; atau mereka dapat disimpan sebagai urutan byte, yang memiliki endianness (setiap pasangan byte berturut-turut dapat mewakilichar16_t
nilai yang berbeda tergantung pada endianness). Standar ini mendukung kedua bentuk ini. Urutanchar16_t
lebih berguna untuk manipulasi internal dalam program. Urutan byte adalah cara untuk bertukar string seperti itu dengan dunia eksternal. Istilah yang akan saya gunakan daripada "byte" dan "lebar" dengan demikian "serial" dan "deserialized".‡ Jika Anda akan mengatakan "tetapi Windows!" pegang 🐎🐎 Anda . Semua versi Windows sejak Windows 2000 menggunakan UTF-16.
☦ Ya, saya tahu tentang großes Eszett (ẞ), tetapi bahkan jika Anda mengubah semua bahasa Jerman semalam menjadi ß huruf besar menjadi ẞ, masih ada banyak kasus lain di mana ini akan gagal. Coba gunakan huruf besar U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. Tidak ada ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; hanya naik menjadi dua Fs. Atau U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; tidak ada modal yang dikompilasi sebelumnya; hanya naik menjadi huruf J besar dan huruf menggabungkan.
sumber
Unicode tidak didukung oleh Perpustakaan Standar (untuk makna dukungan yang masuk akal).
std::string
tidak lebih baik daristd::vector<char>
: ia sama sekali tidak menyadari Unicode (atau representasi / pengkodean lainnya) dan hanya memperlakukan isinya sebagai gumpalan byte.Jika Anda hanya perlu menyimpan dan menyumbat gumpalan , itu bekerja dengan cukup baik; tetapi begitu Anda menginginkan fungsionalitas Unicode (jumlah titik kode , jumlah grafik, dll.) Anda kurang beruntung.
Satu-satunya perpustakaan komprehensif yang saya ketahui adalah ICU . Antarmuka C ++ berasal dari Java, jadi itu jauh dari idiomatik.
sumber
Anda dapat dengan aman menyimpan UTF-8 dalam
std::string
(atau dalamchar[]
atauchar*
, dalam hal ini), karena fakta bahwa Unicode NUL (U + 0000) adalah byte nol di UTF-8 dan bahwa ini adalah satu-satunya cara nol byte dapat terjadi di UTF-8. Oleh karena itu, string UTF-8 Anda akan diakhiri dengan benar sesuai dengan semua fungsi string C dan C ++, dan Anda dapat menggantinya dengan C ++ iostreams (termasukstd::cout
danstd::cerr
, selama lokal Anda adalah UTF-8).Apa yang tidak dapat Anda lakukan dengan
std::string
untuk UTF-8 adalah mendapatkan panjang dalam poin kode.std::string::size()
akan memberi tahu Anda panjang string dalam byte , yang hanya sama dengan jumlah titik kode ketika Anda berada dalam subset ASCII dari UTF-8.Jika Anda perlu beroperasi pada string UTF-8 pada level titik kode (yaitu tidak hanya menyimpan dan mencetaknya) atau jika Anda berurusan dengan UTF-16, yang kemungkinan memiliki banyak byte null internal, Anda perlu melihat ke dalam tipe string karakter lebar.
sumber
std::string
dapat dilempar ke iostreams dengan embedded nulls baik-baik saja.c_str()
sama sekali karenasize()
masih berfungsi. Hanya API yang rusak (yaitu yang tidak bisa menangani embedded nulls seperti sebagian besar dunia C) yang rusak.c_str()
karenac_str()
seharusnya mengembalikan data sebagai string C yang diakhiri null --- yang tidak mungkin, karena fakta bahwa string C tidak dapat menanamkan nulls.c_str()
sekarang hanya mengembalikan sama dengandata()
, yaitu semuanya. API yang mengambil ukuran dapat mengkonsumsinya. API yang tidak, tidak bisa.c_str()
memastikan hasilnya diikuti oleh objek seperti NUL char, dan saya pikirdata()
tidak. Tidak, sepertinyadata()
sekarang juga begitu. (Tentu saja, ini tidak perlu untuk API yang menggunakan ukuran alih-alih menyimpulkannya dari pencarian terminator)C ++ 11 memiliki beberapa tipe string literal baru untuk Unicode.
Sayangnya dukungan di perpustakaan standar untuk pengkodean yang tidak seragam (seperti UTF-8) masih buruk. Misalnya tidak ada cara yang bagus untuk mendapatkan panjang (dalam kode-poin) dari string UTF-8.
sumber
std::string
dapat menahan string UTF-8 tanpa masalah, tetapi mis.length
Metode mengembalikan jumlah byte dalam string dan bukan jumlah titik kode.ñ
sebagai 'SURAT KECIL LATIN N DENGAN TILDE' (U + 00F1) (yang merupakan satu titik kode) atau 'LATIN KECIL SURAT N' ( U + 006E) diikuti oleh 'COMBINING TILDE' (U + 0303) yang merupakan dua titik kode.LATIN SMALL LETTER N'
== atau tidak(U+006E) followed by 'COMBINING TILDE' (U+0303)
.Namun, ada perpustakaan yang cukup berguna yang disebut tiny-utf8 , yang pada dasarnya adalah pengganti drop-in untuk
std::string
/std::wstring
. Ini bertujuan untuk mengisi celah dari kelas kontainer utf8-string yang masih hilang.Ini mungkin cara yang paling nyaman untuk 'berurusan' dengan utf8 string (yaitu, tanpa normalisasi unicode dan hal-hal serupa). Anda dapat beroperasi dengan nyaman pada codepoint , sementara string Anda tetap dikodekan dalam run-length-encoded
char
.sumber