Seberapa baik Unicode didukung di C ++ 11?

183

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?
Ralph Tandetzky
sumber
19
"Apakah std :: string melakukan apa yang seharusnya?" Menurut Anda apa yang harus dilakukan?
R. Martinho Fernandes
2
Saya menggunakan utfcpp.sourceforge.net untuk kebutuhan utf8 saya. File header sederhana yang menyediakan iterator untuk string unicode.
fscan
2
std :: string harus menyimpan byte, yaitu urutan unit kode dari pengkodean UTF-8. Ya, memang begitu, sejak awal. utf8everywhere.org
Pavel Radzivilovsky
3
Masalah potensial terbesar dengan dukungan Unicode terletak di dalam Unicode dan penggunaannya dalam teknologi informasi itu sendiri. Unicode tidak cocok (dan tidak dirancang) untuk apa itu digunakan. Unicode dirancang untuk mereproduksi setiap mesin terbang yang mungkin telah ditulis di suatu tempat oleh seseorang, pada suatu waktu dengan setiap nuansa yang tidak mungkin dan menyolok mungkin, termasuk 3 atau 4 arti yang berbeda dan 3 atau 4 cara berbeda untuk menyusun mesin terbang yang sama. Ini tidak dimaksudkan untuk menjadi berguna untuk digunakan dalam bahasa sehari-hari, dan itu tidak dimaksudkan untuk dapat diterapkan atau untuk diproses dengan mudah atau tidak ambigu.
Damon
11
Ya itu dirancang untuk digunakan untuk bahasa sehari-hari. Milik saya setidaknya. Dan milik Anda kemungkinan besar juga. Ternyata memproses teks manusia secara umum adalah tugas yang sangat sulit. Bahkan tidak mungkin untuk mendefinisikan secara gamblang apa karakter itu. Reproduksi mesin terbang umum bahkan tidak benar-benar bagian dari piagam Unicode.
Jean-Denis Muys

Jawaban:

267

Seberapa baik pustaka standar C ++ mendukung unicode?

Sangat.

Pemindaian cepat melalui fasilitas perpustakaan yang mungkin menyediakan dukungan Unicode memberi saya daftar ini:

  • Perpustakaan string
  • Perpustakaan lokalisasi
  • Pustaka input / output
  • Pustaka ekspresi reguler

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.

Apakahstd::string melakukan apa yang seharusnya?

Iya. Menurut standar C ++, inilah yang std::stringharus dilakukan dan saudara-saudaranya:

Templat kelas basic_stringmenjelaskan objek yang dapat menyimpan urutan yang terdiri dari sejumlah objek char-arbitrary dengan elemen pertama dari urutan di posisi nol.

Nah, std::stringapakah itu baik-baik saja. Apakah itu menyediakan fungsionalitas khusus Unicode? Tidak.

Haruskah itu Mungkin tidak. std::stringbaik-baik saja sebagai urutan charobjek. Itu berguna; satu-satunya gangguan adalah bahwa itu adalah tampilan yang sangat rendah dari teks dan standar C ++ tidak memberikan yang lebih tinggi.

Bagaimana saya menggunakannya?

Gunakan sebagai urutan charobjek; berpura-pura itu adalah sesuatu yang lain pasti berakhir dengan rasa sakit.

Di mana ada potensi masalah?

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/ mbrtoc16dan c32rtomb/ 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:

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);
// ...

Bagaimana Anda mengharapkan salah satu dari fungsi-fungsi ini untuk mengkategorikan dengan benar, misalnya, U + 1F34C ʙᴀɴᴀɴᴀ, seperti pada u8"🍌"atau u8"\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_thanya menggunakan :U'\U0001F34C' adalah unit kode tunggal di UTF-32.

Namun, itu tetap berarti Anda hanya mendapatkan transformasi casing sederhana dengan toupperdan tolower, yang, misalnya, tidak cukup baik untuk beberapa lokal Jerman: "ß" huruf besar menjadi "SS" ☦ tetapi toupperhanya dapat mengembalikan satu karakter unit kode .

Selanjutnya, wstring_convert/ wbuffer_convertdan aspek konversi kode standar.

wstring_convertdigunakan 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_convertmelakukan fungsi yang serupa tetapi sebagai buffer aliran deserialized lebar yang membungkus byte stream buffer serial. Setiap I / O dilakukan melalui buffer aliran serial byte yang mendasari 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 beberapa codecvtspesialisasi. 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).

  • UTF-8 ↔ UCS-2 dengan codecvt_utf8<char16_t>, dan di codecvt_utf8<wchar_t>mana sizeof(wchar_t) == 2;
  • UTF-8 ↔ UTF-32 dengan codecvt_utf8<char32_t>, codecvt<char32_t, char, mbstate_t>, dan codecvt_utf8<wchar_t>di mana sizeof(wchar_t) == 4;
  • UTF-16 ↔ UCS-2 dengan codecvt_utf16<char16_t>, dan di codecvt_utf16<wchar_t>mana sizeof(wchar_t) == 2;
  • UTF-16 ↔ UTF-32 dengan codecvt_utf16<char32_t>, dan di codecvt_utf16<wchar_t>mana sizeof(wchar_t) == 4;
  • UTF-8 ↔ UTF-16 dengan codecvt_utf8_utf16<char16_t>, codecvt<char16_t, char, mbstate_t>, dan codecvt_utf8_utf16<wchar_t>di manasizeof(wchar_t) == 2 ;
  • sempit ↔ lebar dengan codecvt<wchar_t, char_t, mbstate_t>
  • no-op dengan 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_tini 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_convertdan wbuffer_convertfasilitas 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.

Itu dia?

Ya itu saja. Itulah fungsionalitas yang ada. Ada banyak fungsi Unicode yang tidak terlihat seperti normalisasi atau algoritma segmentasi teks.

U + 1F4A9 . Apakah ada cara untuk mendapatkan dukungan Unicode yang lebih baik di C ++?

Tersangka yang biasa: ICU dan Boost.Locale .


String String byte adalah, tidak mengherankan, string byte, yaitu charobjek. Namun, tidak seperti string string literal , yang selalu merupakan array wchar_tobjek, "string lebar" dalam konteks ini tidak harus berupa string wchar_tobjek. 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 mewakili char16_tnilai yang berbeda tergantung pada endianness). Standar ini mendukung kedua bentuk ini. Urutan char16_tlebih 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.

R. Martinho Fernandes
sumber
26
Semakin saya membacanya, semakin saya merasa tidak mengerti apa-apa tentang semua ini. Saya membaca sebagian besar dari hal ini beberapa bulan yang lalu dan masih merasa seperti saya menemukan semuanya lagi ... Agar sederhana bagi otak saya yang malang yang sekarang agak sakit, semua saran di utf8 di mana -mana masih berlaku, Baik? Jika saya "hanya" ingin agar pengguna saya dapat membuka dan menulis file tidak peduli pengaturan sistem mereka, saya dapat meminta mereka nama file, menyimpannya dalam std :: string dan semuanya harus bekerja dengan baik, bahkan pada Windows? Maaf meminta itu (lagi) ...
Uflex
5
@Uflex Yang benar - benar dapat Anda lakukan dengan std :: string adalah memperlakukannya sebagai gumpalan biner. Dalam implementasi Unicode yang tepat, baik internal (karena tersembunyi jauh dalam detail implementasi) maupun masalah pengkodean eksternal (well, agak, Anda masih perlu memiliki encoder / decoder tersedia).
Cat Plus Plus
3
@Uflex mungkin. Saya tidak tahu apakah mengikuti saran yang tidak Anda pahami adalah ide yang bagus.
R. Martinho Fernandes
1
Ada proposal untuk dukungan Unicode di C ++ 2014/17. Namun itu 1, mungkin 4 tahun lagi dan jarang digunakan sekarang. open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html
graham.reeds
20
@ graham.reeds haha, terima kasih, tapi saya tahu itu. Lihat bagian "Ucapan Terima Kasih";)
R. Martinho Fernandes
40

Unicode tidak didukung oleh Perpustakaan Standar (untuk makna dukungan yang masuk akal).

std::stringtidak lebih baik dari std::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.

Matthieu M.
sumber
2
Bagaimana dengan Boost.Locale ?
Uflex
11
@Uflex: dari halaman yang Anda tautkan untuk mencapai tujuan ini. Boost.Locale menggunakan perpustakaan Unicode dan Pelokalan yang canggih: ICU - Komponen Internasional untuk Unicode.
Matthieu M.
1
Boost.Locale mendukung backend non-ICU lainnya, lihat di sini: boost.org/doc/libs/1_53_0/libs/locale/doc/html/…
Superfly Jon
@ SupupflyJon: Benar, tetapi menurut halaman yang sama, dukungan untuk Unicode dari backend non-ICU adalah "sangat terbatas".
Matthieu M.
24

Anda dapat dengan aman menyimpan UTF-8 dalam std::string(atau dalam char[]atau char*, 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 (termasuk std::coutdan std::cerr, selama lokal Anda adalah UTF-8).

Apa yang tidak dapat Anda lakukan dengan std::stringuntuk 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.

uckelman
sumber
3
std::stringdapat dilempar ke iostreams dengan embedded nulls baik-baik saja.
R. Martinho Fernandes
3
Benar-benar dimaksudkan. Tidak pecah c_str()sama sekali karena size()masih berfungsi. Hanya API yang rusak (yaitu yang tidak bisa menangani embedded nulls seperti sebagian besar dunia C) yang rusak.
R. Martinho Fernandes
1
Embedded nulls break c_str()karena c_str()seharusnya mengembalikan data sebagai string C yang diakhiri null --- yang tidak mungkin, karena fakta bahwa string C tidak dapat menanamkan nulls.
uckelman
4
Tidak lagi. c_str()sekarang hanya mengembalikan sama dengan data(), yaitu semuanya. API yang mengambil ukuran dapat mengkonsumsinya. API yang tidak, tidak bisa.
R. Martinho Fernandes
6
Dengan sedikit perbedaan yang c_str()memastikan hasilnya diikuti oleh objek seperti NUL char, dan saya pikir data()tidak. Tidak, sepertinya data()sekarang juga begitu. (Tentu saja, ini tidak perlu untuk API yang menggunakan ukuran alih-alih menyimpulkannya dari pencarian terminator)
Ben Voigt
8

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.

Beberapa programmer Bung
sumber
Jadi apakah kita masih perlu menggunakan std :: wstring untuk nama file jika kita ingin mendukung bahasa non-latin? Karena literal string baru tidak benar-benar membantu di sini karena string biasanya berasal dari pengguna ...
Uflex
7
@Uflex std::stringdapat menahan string UTF-8 tanpa masalah, tetapi mis. lengthMetode mengembalikan jumlah byte dalam string dan bukan jumlah titik kode.
Beberapa programmer dude
8
Sejujurnya, mendapatkan panjang dalam poin kode string tidak memiliki banyak kegunaan. Panjang dalam byte dapat digunakan untuk mengalokasikan buffer dengan benar, misalnya.
R. Martinho Fernandes
2
Jumlah titik kode dalam string UTF-8 bukan angka yang sangat menarik: Seseorang dapat menulis ñ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.
Martin Bonner mendukung Monica
Semua komentar tentang "Anda tidak memerlukan ini dan Anda tidak perlu" seperti "jumlah poin kode yang tidak penting" dll. Kedengarannya agak mencurigakan bagi saya. Setelah Anda menulis parser yang seharusnya mem-parsing kode sumber utf8, terserah spesifikasi parser apakah ia menganggap LATIN SMALL LETTER N' == atau tidak (U+006E) followed by 'COMBINING TILDE' (U+0303).
BitTickler
4

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.

Jakob Riedle
sumber