ฉันอ่านแล้วได้ยินว่า C ++ 11 รองรับ Unicode คำถามสองสามข้อเกี่ยวกับเรื่องนี้:
- ไลบรารีมาตรฐาน C ++ รองรับ Unicode ได้ดีแค่ไหน
- ไม่
std::string
ทำในสิ่งที่ควร? - ฉันจะใช้มันได้อย่างไร
- ปัญหาที่อาจเกิดขึ้นอยู่ที่ไหน
ฉันอ่านแล้วได้ยินว่า C ++ 11 รองรับ Unicode คำถามสองสามข้อเกี่ยวกับเรื่องนี้:
std::string
ทำในสิ่งที่ควร?คำตอบ:
ไลบรารี่มาตรฐาน C ++ รองรับ Unicode ได้ดีแค่ไหน?
ชะมัด.
การสแกนอย่างรวดเร็วผ่านสิ่งอำนวยความสะดวกห้องสมุดที่อาจให้การสนับสนุน Unicode ให้ฉันรายการนี้:
ฉันคิดว่าทั้งหมด แต่คนแรกให้การสนับสนุนที่น่ากลัว ฉันจะกลับไปที่รายละเอียดเพิ่มเติมหลังจากผ่านคำถามอื่นของคุณอย่างรวดเร็ว
ไม่
std::string
ทำในสิ่งที่ควร?
ใช่. ตามมาตรฐาน C ++ นี่คือสิ่งที่std::string
และพี่น้องควรทำ:
เท็มเพลตคลาส
basic_string
อธิบายถึงวัตถุที่สามารถเก็บลำดับที่ประกอบด้วยจำนวน char-like object ที่แตกต่างกันจำนวนหนึ่งด้วยองค์ประกอบแรกของลำดับที่ตำแหน่งศูนย์
ดีstd::string
ไม่ว่าเพียงแค่ปรับ นั่นมีฟังก์ชั่นเฉพาะ Unicode หรือไม่? เลขที่
ควรเป็น? อาจจะไม่. std::string
เป็นไปตามลำดับของchar
วัตถุ นั่นเป็นประโยชน์ สิ่งเดียวที่น่ารำคาญก็คือมันเป็นมุมมองที่ต่ำมากของข้อความและ C ++ มาตรฐานไม่ได้ให้มุมมองที่สูงกว่า
ฉันจะใช้มันได้อย่างไร
ใช้เป็นลำดับของchar
วัตถุ การแกล้งทำเป็นว่ามันเป็นเรื่องอื่นที่ต้องจบลงด้วยความเจ็บปวด
ปัญหาที่อาจเกิดขึ้นอยู่ที่ไหน
ทั่วทุกสถานที่? มาดูกัน...
ไลบรารีสตริง
ห้องสมุดสตริงให้เราbasic_string
ซึ่งเป็นเพียงลำดับของสิ่งที่มาตรฐานเรียกว่า "วัตถุคล้ายถ่าน" ฉันเรียกพวกเขาว่าหน่วยรหัส หากคุณต้องการมุมมองระดับสูงของข้อความนี่ไม่ใช่สิ่งที่คุณกำลังมองหา นี่คือมุมมองของข้อความที่เหมาะสมสำหรับการทำให้เป็นอนุกรม / deserialization / storage
นอกจากนี้ยังมีเครื่องมือบางอย่างจากห้องสมุด C ที่สามารถนำมาใช้เพื่อลดช่องว่างระหว่างโลกแคบและโลก Unicode นี้c16rtomb
/ mbrtoc16
และ/c32rtomb
mbrtoc32
ห้องสมุดรองรับหลายภาษา
ห้องสมุดการแปลยังคงเชื่อว่าหนึ่งใน "วัตถุที่มีลักษณะคล้ายถ่าน" หนึ่งในนั้นมีค่าเท่ากับ "อักขระ" หนึ่งตัว แน่นอนว่ามันโง่และทำให้มันเป็นไปไม่ได้ที่จะได้รับสิ่งต่าง ๆ มากมายทำงานอย่างถูกต้องนอกเหนือจากชุดย่อยของ Unicode เช่น ASCII
ลองพิจารณาตัวอย่างเช่นสิ่งที่มาตรฐานเรียกว่า "ความสะดวกสบาย" ใน<locale>
ส่วนหัว:
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);
// ...
คุณคาดหวังว่าฟังก์ชั่นใด ๆ เหล่านี้จะจัดหมวดหมู่พูดอย่างถูกต้อง U + 1F34C ʙᴀɴᴀɴᴀเช่นเดียวกับในu8"🍌"
หรือu8"\U0001F34C"
อย่างไร มันไม่มีทางที่จะทำงานได้เพราะฟังก์ชั่นเหล่านั้นใช้หน่วยรหัสเดียวเป็นอินพุต
สิ่งนี้สามารถทำงานกับโลแคลที่เหมาะสมหากคุณใช้char32_t
เท่านั้น: U'\U0001F34C'
เป็นหน่วยรหัสเดียวใน UTF-32
อย่างไรก็ตามนั่นก็หมายความว่าคุณจะได้รับการแปลงแบบง่าย ๆ ด้วยtoupper
และtolower
เท่านั้นซึ่งไม่ดีพอสำหรับบางภาษาเยอรมัน: "ß" พิมพ์ใหญ่เป็น "SS" ☦ แต่toupper
สามารถส่งคืนหน่วยอักขระได้หนึ่งหน่วยเท่านั้น
ขั้นต่อไปwstring_convert
/ wbuffer_convert
และการแปลงรหัสมาตรฐานเป็นส่วน
wstring_convert
จะใช้ในการแปลงระหว่างสตริงในการเข้ารหัสที่กำหนดให้เป็นสตริงในการเข้ารหัสที่กำหนดอื่น มีสองประเภทสตริงที่เกี่ยวข้องในการแปลงนี้ซึ่งมาตรฐานเรียกสตริงไบต์และสตริงกว้าง เนื่องจากข้อกำหนดเหล่านี้ทำให้เข้าใจผิดจริงๆฉันจึงต้องการใช้ "ต่อเนื่อง" และ "ดีซีเรียลไลซ์" ตามลำดับแทน†
การเข้ารหัสการแปลงระหว่างมีการตัดสินใจโดย codecvt (แง่โค้ด Conversion) wstring_convert
ผ่านเป็นอาร์กิวเมนต์ชนิดแม่แบบ
wbuffer_convert
ดำเนินการฟังก์ชั่นที่คล้ายกัน แต่เป็นบัฟเฟอร์กระแสกว้าง deserialized ที่ห่อบัฟเฟอร์กระแสไบต์อนุกรม I / O ใด ๆ จะดำเนินการผ่านบัฟเฟอร์กระแสไบต์พื้นฐานที่มีการแปลงไปและกลับจากการเข้ารหัสที่กำหนดโดยอาร์กิวเมนต์ codecvt การเขียนซีเรียลไลซ์ลงในบัฟเฟอร์นั้นจากนั้นเขียนจากมันและการอ่านจะอ่านลงในบัฟเฟอร์และจากนั้นทำการดีซีเรียลไลซ์
มาตรฐานการให้บางส่วนแม่แบบระดับ codecvt สำหรับใช้กับสิ่งอำนวยความสะดวกเหล่านี้: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
และบางส่วนcodecvt
เฉพาะด้าน ร่วมกัน facets มาตรฐานเหล่านี้ให้การแปลงต่อไปนี้ทั้งหมด (หมายเหตุ: ในรายการต่อไปนี้การเข้ารหัสทางด้านซ้ายจะเป็นสตริง / streambuf ที่ต่อเนื่องกันเสมอและการเข้ารหัสทางด้านขวาจะเป็นสตริง / streambuf ที่ดีซีเรียลไลซ์เสมอซึ่งเป็นมาตรฐานที่อนุญาตการแปลงในทั้งสองทิศทาง)
codecvt_utf8<char16_t>
, และcodecvt_utf8<wchar_t>
ที่ไหนsizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
และcodecvt_utf8<wchar_t>
ที่sizeof(wchar_t) == 4
;codecvt_utf16<char16_t>
, และcodecvt_utf16<wchar_t>
ที่ไหนsizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
, และcodecvt_utf16<wchar_t>
ที่ไหนsizeof(wchar_t) == 4
;codecvt_utf8_utf16<char16_t>
, codecvt<char16_t, char, mbstate_t>
และcodecvt_utf8_utf16<wchar_t>
ที่sizeof(wchar_t) == 2
;codecvt<wchar_t, char_t, mbstate_t>
codecvt<char, char, mbstate_t>
กับหลายสิ่งเหล่านี้มีประโยชน์ แต่มีสิ่งที่น่าอึดอัดใจอยู่มากมายที่นี่
ก่อนอื่น - ตัวแทนระดับสูงศักดิ์สิทธิ์! รูปแบบการตั้งชื่อนั้นยุ่ง
จากนั้นมีการสนับสนุน UCS-2 มากมาย UCS-2 เป็นการเข้ารหัสจาก Unicode 1.0 ที่ถูกแทนที่ในปี 1996 เพราะรองรับเฉพาะเครื่องบินแบบหลายภาษาขั้นพื้นฐานเท่านั้น เหตุใดคณะกรรมการจึงคิดว่าเป็นที่น่าพอใจที่จะให้ความสำคัญกับการเข้ารหัสที่ถูกแทนที่เมื่อ 20 ปีก่อนฉันไม่รู้‡ มันไม่เหมือนกับการรองรับการเข้ารหัสเพิ่มเติมที่ไม่ดีหรืออะไรก็ตาม แต่ UCS-2 ปรากฏบ่อยเกินไปที่นี่
ฉันจะบอกว่าchar16_t
มีความหมายชัดเจนสำหรับการจัดเก็บหน่วยรหัส UTF-16 อย่างไรก็ตามนี่เป็นส่วนหนึ่งของมาตรฐานที่คิดเป็นอย่างอื่น codecvt_utf8<char16_t>
ไม่มีอะไรเกี่ยวข้องกับ UTF-16 ตัวอย่างเช่นwstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
จะคอมไพล์ได้ดี แต่จะล้มเหลวโดยไม่มีเงื่อนไข: อินพุตจะถูกใช้เป็นสตริง UCS-2 u"\xD83C\xDF4C"
ซึ่งไม่สามารถแปลงเป็น UTF-8 ได้เนื่องจาก UTF-8 ไม่สามารถเข้ารหัสค่าใด ๆ ในช่วง 0xD800-0xDFFF
ยังคงอยู่ที่ด้านหน้า UCS-2 ไม่มีวิธีการอ่านจากสตรีม UTF-16 ไบต์ไปยังสตริง UTF-16 ด้วย facets เหล่านี้ หากคุณมีลำดับ UTF-16 ไบต์คุณจะไม่สามารถทำการ deserialize ให้เป็นสตริงchar16_t
ได้ นี่เป็นเรื่องที่น่าแปลกใจเพราะมันเป็นการแปลงอัตลักษณ์ที่มากหรือน้อย แม้ว่าที่น่าแปลกใจมากขึ้นคือความจริงที่ว่ามีการสนับสนุนการดีซีเรียลไลซ์จากสตรีม UTF-16 ไปเป็นสตริง UCS-2 ด้วยcodecvt_utf16<char16_t>
ซึ่งจริงๆแล้วเป็นการแปลงที่สูญเสียไป
การสนับสนุน UTF-16-as-bytes นั้นค่อนข้างดี แต่มันรองรับการตรวจจับ endianess จาก BOM หรือเลือกอย่างชัดเจนในโค้ด นอกจากนี้ยังรองรับการผลิตผลผลิตที่มีและไม่มี BOM
มีความเป็นไปได้ในการแปลงที่น่าสนใจอีกมากที่ขาดไป ไม่มีวิธีใดในการดีซีเรียลไลซ์จากสตรีมหรือสตริง UTF-16 ลงในสตริง UTF-8 เนื่องจาก UTF-8 ไม่ได้รับการสนับสนุนในรูปแบบที่ดีซีเรียลไลซ์
และที่นี่โลกแคบ / กว้างแยกจากโลก UTF / UCS อย่างสมบูรณ์ ไม่มีการแปลงระหว่างการเข้ารหัสแบบแคบ / กว้างแบบเก่ากับการเข้ารหัสแบบ Unicode
ไลบรารีอินพุต / เอาต์พุต
ไลบรารี I / O สามารถใช้เพื่ออ่านและเขียนข้อความในการเข้ารหัส Unicode โดยใช้wstring_convert
และwbuffer_convert
สิ่งอำนวยความสะดวกที่อธิบายไว้ข้างต้น ฉันไม่คิดว่าจะมีอะไรอีกมากมายที่จะต้องได้รับการสนับสนุนจากห้องสมุดมาตรฐานนี้
ไลบรารีนิพจน์ปกติ
ฉันได้อธิบายเกี่ยวกับปัญหากับC ++ regexes และ Unicodeใน Stack Overflow มาก่อน ฉันจะไม่ทำซ้ำจุดเหล่านี้ทั้งหมด แต่เพียงระบุว่า C ++ regexes ไม่มีการสนับสนุน Unicode ระดับ 1 ซึ่งเป็นขั้นต่ำที่เปลือยเปล่าเพื่อให้สามารถใช้งานได้โดยไม่ต้องใช้ UTF-32 ทุกที่
แค่นั้นแหละ?
ใช่แค่นั้นแหละ นั่นคือฟังก์ชั่นที่มีอยู่ มีฟังก์ชัน Unicode มากมายที่ไม่สามารถมองเห็นได้เช่นการทำให้เป็นมาตรฐานหรืออัลกอริทึมการแบ่งส่วนข้อความ
U + 1F4A9 มีวิธีใดบ้างที่จะได้รับการสนับสนุน Unicode ที่ดีขึ้นใน C ++?
สงสัยปกติ: ห้องไอซียูและBoost.Locale
string สตริงไบต์ไม่น่าแปลกใจคือสตริงไบต์เช่นchar
วัตถุ อย่างไรก็ตามแตกต่างจากตัวอักษรสตริงกว้างซึ่งมักจะเป็นอาร์เรย์ของwchar_t
วัตถุ "สตริงกว้าง" ในบริบทนี้ไม่จำเป็นต้องเป็นสตริงของwchar_t
วัตถุ ในความเป็นจริงมาตรฐานไม่ได้กำหนดอย่างชัดเจนว่า "wide string" หมายถึงอะไรดังนั้นเราจึงเหลือที่จะเดาความหมายจากการใช้งาน เนื่องจากคำศัพท์มาตรฐานนั้นเลอะเทอะและสับสนฉันจึงใช้ชื่อของฉันเองในความชัดเจน
การเข้ารหัสเช่น UTF-16 สามารถจัดเก็บเป็นลำดับของchar16_t
ซึ่งไม่มีความเอนเอียง หรือพวกเขาสามารถเก็บไว้เป็นลำดับของไบต์ซึ่งมี endianness (ไบต์คู่ต่อเนื่องแต่ละคู่สามารถแสดงchar16_t
ค่าที่แตกต่างกันขึ้นอยู่กับ endianness) มาตรฐานรองรับทั้งสองรูปแบบเหล่านี้ ลำดับของchar16_t
มีประโยชน์มากสำหรับการจัดการภายในในโปรแกรม ลำดับของไบต์เป็นวิธีการแลกเปลี่ยนสตริงดังกล่าวกับโลกภายนอก คำที่ฉันจะใช้แทน "ไบต์" และ "กว้าง" จึงเป็น "อนุกรม" และ "ดีซีเรียลไลซ์"
‡หากคุณกำลังจะพูดว่า "แต่ใช้ Windows!" ถือของคุณ🐎🐎 Windows ทุกรุ่นตั้งแต่ Windows 2000 ใช้ UTF-16
☦ใช่ฉันรู้เกี่ยวกับgroßes Eszett (ẞ) แต่แม้ว่าคุณจะเปลี่ยนสถานที่เยอรมันทั้งหมดในชั่วข้ามคืนเพื่อรับßตัวพิมพ์ใหญ่เป็นẞยังมีอีกหลายกรณีที่สิ่งนี้จะล้มเหลว ลองใช้ตัวพิมพ์ใหญ่ U + FB00 ʟᴀᴛɪɴsᴍᴀʟʟʟɪɢᴀᴛᴜʀᴇғғ ไม่มีʟᴀᴛɪɴᴄᴀᴘɪᴛᴀʟʟɪɢᴀᴛᴜʀᴇғғ; มันเป็นตัวพิมพ์ใหญ่ถึงสอง Fs หรือ U + 01F0 ʟᴀᴛɪɴsᴍᴀʟʟʟᴇᴛᴛᴇʀᴊᴡɪᴛʜᴄᴀʀᴏɴ; ไม่มีทุน precomposed; มันเป็นตัวพิมพ์ใหญ่ถึงเมืองหลวง J และรอนรวมกัน
ไลบรารี่มาตรฐานไม่รองรับ Unicode (สำหรับความหมายที่สมเหตุสมผลตามสมควร)
std::string
ไม่ดีกว่าstd::vector<char>
: มันเป็นสมบูรณ์ลบเลือนไป Unicode (หรืออื่น ๆ การแสดง / การเข้ารหัส) และก็รักษาเนื้อหาที่เป็นหยดไบต์
หากคุณต้องการจัดเก็บและจัดการblobsเท่านั้นมันใช้งานได้ดี แต่ทันทีที่คุณต้องการฟังก์ชั่น Unicode (จำนวนคะแนนโค้ด , จำนวนภาพฯลฯ ) คุณจะโชคไม่ดี
เพียงห้องสมุดที่ครอบคลุมฉันรู้ว่านี้คือห้องไอซียู อินเทอร์เฟซ C ++ นั้นได้มาจาก Java หนึ่งดังนั้นจึงยังห่างไกลจากการใช้สำนวน
คุณสามารถเก็บ UTF-8 ได้อย่างปลอดภัยในstd::string
(หรือในchar[]
หรือchar*
สำหรับเรื่องนั้น) เนื่องจากความจริงที่ว่า Unicode NUL (U + 0000) เป็นไบต์ว่างใน UTF-8 และนี่เป็นวิธีเดียวที่เป็นโมฆะ ไบต์สามารถเกิดขึ้นใน UTF-8 ดังนั้นสตริง UTF-8 ของคุณจะถูกยกเลิกอย่างถูกต้องตามฟังก์ชั่นสตริง C และ C ++ ทั้งหมดและคุณสามารถโยงมันด้วย C ++ iostreams (รวมถึงstd::cout
และstd::cerr
ตราบใดที่โลแคลของคุณคือ UTF-8)
สิ่งที่คุณไม่สามารถทำได้std::string
สำหรับ UTF-8 นั้นคือความยาวในจุดโค้ด std::string::size()
จะบอกความยาวสตริงเป็นไบต์ซึ่งเท่ากับจำนวนคะแนนโค้ดเฉพาะเมื่อคุณอยู่ในชุดย่อย ASCII ของ UTF-8
หากคุณต้องการใช้งานกับสตริง UTF-8 ที่ระดับรหัสจุด (เช่นไม่ใช่แค่จัดเก็บและพิมพ์) หรือถ้าคุณกำลังจัดการกับ UTF-16 ซึ่งมีแนวโน้มว่าจะมีไบต์ว่างภายในจำนวนมากคุณต้องพิจารณา ประเภทสตริงตัวกว้าง
std::string
สามารถโยนเข้าไปใน iostreams ด้วย nulls ฝังตัวได้ดี
c_str()
เลยเพราะsize()
ยังใช้งานได้ API ที่ใช้งานไม่ได้เท่านั้น (เช่นที่ไม่สามารถจัดการกับโมฆะแบบฝังตัวเช่นเดียวกับโลก C ส่วนใหญ่)
c_str()
เนื่องจากc_str()
ควรส่งคืนข้อมูลเป็นสตริง C ที่สิ้นสุดด้วยค่า null ซึ่งเป็นไปไม่ได้เนื่องจากความจริงที่ว่าสตริง C ไม่สามารถมีค่า null ที่ฝังอยู่ได้
c_str()
ตอนนี้เพียงแค่ส่งกลับเช่นเดียวกับdata()
ทุกอย่าง API ที่มีขนาดสามารถบริโภคได้ API ที่ทำไม่ได้ไม่สามารถทำได้
c_str()
ทำให้แน่ใจว่าผลลัพธ์ตามด้วยวัตถุที่คล้ายกับ NUL และฉันไม่คิดว่าdata()
จะเป็นเช่นนั้น ไม่ดูเหมือนdata()
ตอนนี้ก็ทำเช่นกัน (ของหลักสูตรนี้ไม่จำเป็นสำหรับ API ที่ใช้ขนาดแทนการอนุมานได้จากการค้นหาของเทอร์มิก)
C ++ 11 มีสตริงตัวอักษรใหม่สองชนิดสำหรับ Unicode
น่าเสียดายที่การสนับสนุนในไลบรารีมาตรฐานสำหรับการเข้ารหัสที่ไม่สม่ำเสมอ (เช่น UTF-8) ยังคงไม่ดี ตัวอย่างเช่นไม่มีวิธีที่ดีในการรับความยาว (เป็นรหัสจุด) ของสตริง UTF-8
std::string
สามารถเก็บสตริง UTF-8 ได้โดยไม่มีปัญหา แต่เช่นlength
วิธีการส่งคืนจำนวนไบต์ในสตริงและไม่ใช่จำนวนของรหัสจุด
ñ
เป็น 'LATIN SMALL LETTER N WITH TILDE' (U + 00F1) (ซึ่งเป็นจุดรหัสหนึ่ง) หรือ 'LATIN SMALL LETTER N' ( U + 006E) ตามด้วย 'COMBINING TILDE' (U + 0303) ซึ่งเป็นจุดรหัสสองจุด
LATIN SMALL LETTER N'
(U+006E) followed by 'COMBINING TILDE' (U+0303)
แต่มีเป็นห้องสมุดที่มีประโยชน์สวยที่เรียกว่าเล็ก ๆ -utf8ซึ่งเป็นพื้นดรอปแทนสำหรับ/std::string
std::wstring
มันมีจุดมุ่งหมายเพื่อเติมเต็มช่องว่างของคลาสคอนเทนเนอร์ utf8-string ที่หายไป
นี่อาจเป็นวิธีที่สะดวกสบายที่สุดในการ 'จัดการ' กับสตริง utf8 (นั่นคือไม่มีการทำให้เป็นมาตรฐานแบบ Unicode และสิ่งที่คล้ายกัน) คุณสบายทำงานบนcodepointsในขณะที่การเข้าพักสายของคุณเข้ารหัสในระยะยาวเข้ารหัสchar
s