"ผิด" อะไรกับ C ++ wchar_t และ wstrings มีทางเลือกใดบ้างสำหรับอักขระแบบกว้าง


87

ฉันเคยเห็นผู้คนจำนวนมากในชุมชน C ++ (โดยเฉพาะ ## c ++ บน freenode) ไม่พอใจการใช้wstringsและwchar_tและการใช้งานของพวกเขาใน windows api อะไรคือสิ่งที่ "ผิด" กันแน่wchar_tและwstringและถ้าฉันต้องการสนับสนุนความเป็นสากลจะมีทางเลือกอื่นใดบ้างสำหรับอักขระแบบกว้าง


1
มีข้อมูลอ้างอิงหรือไม่?
Dani

14
บางทีกระทู้ที่ยอดเยี่ยมนี้จะตอบทุกคำถามของคุณ stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox

15
ใน Windows คุณไม่มีทางเลือกจริงๆ API ภายในได้รับการออกแบบมาสำหรับ UCS-2 ซึ่งมีความสมเหตุสมผลในเวลานั้นเนื่องจากเป็นช่วงก่อนการเข้ารหัส UTF-8 และ UTF-16 ที่มีความยาวผันแปรได้ แต่ตอนนี้พวกเขารองรับ UTF-16 แล้วพวกเขาก็จบลงด้วยความเลวร้ายที่สุดของทั้งสองโลก
jamesdlin

12
utf8everywhere.orgมีการอภิปรายถึงเหตุผลที่ดีในการหลีกเลี่ยงอักขระที่กว้าง
JoeG

5
@jamesdlin แน่นอนคุณมีทางเลือก ไลบรารี nowide เป็นวิธีที่สะดวกในการแปลงสตริงเมื่อส่งผ่านไปยัง API การเรียก API ด้วยสตริงมักมีความถี่ต่ำดังนั้นวิธีที่เหมาะสมคือการแปลง ad-hok และมีไฟล์และตัวแปรภายในใน UTF-8 ตลอดเวลา
Pavel Radzivilovsky

คำตอบ:


115

wchar_t คืออะไร?

wchar_t ถูกกำหนดให้การเข้ารหัส char ของโลแคลใด ๆ สามารถแปลงเป็นการแสดง wchar_t โดยที่ wchar_t ทุกตัวแสดงถึงจุดรหัสเดียว:

ประเภท wchar_t เป็นประเภทที่แตกต่างกันซึ่งค่าสามารถแสดงรหัสที่แตกต่างกันสำหรับสมาชิกทั้งหมดของชุดอักขระเพิ่มเติมที่ใหญ่ที่สุดที่ระบุระหว่างโลแคลที่รองรับ (22.3.1)

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

สิ่งนี้ไม่ต้องการให้ wchar_t มีขนาดใหญ่พอที่จะแสดงอักขระใด ๆ จากทุกภาษาพร้อมกัน นั่นคือการเข้ารหัสที่ใช้สำหรับ wchar_t อาจแตกต่างกันระหว่างโลแคล ซึ่งหมายความว่าคุณไม่จำเป็นต้องแปลงสตริงเป็น wchar_t โดยใช้โลแคลเดียวแล้วแปลงกลับเป็น char โดยใช้โลแคลอื่น 1

เนื่องจากการใช้ wchar_t เป็นตัวแทนทั่วไประหว่างโลแคลทั้งหมดดูเหมือนจะเป็นการใช้งานหลักสำหรับ wchar_t ในทางปฏิบัติคุณอาจสงสัยว่ามันดีสำหรับอะไรถ้าไม่ใช่อย่างนั้น

ความตั้งใจและวัตถุประสงค์ดั้งเดิมของ wchar_t คือการทำให้การประมวลผลข้อความเป็นเรื่องง่ายโดยกำหนดให้ต้องมีการแมปแบบหนึ่งต่อหนึ่งจากหน่วยรหัสของสตริงไปยังอักขระของข้อความจึงอนุญาตให้ใช้อัลกอริทึมแบบง่าย ๆ เช่นเดียวกับที่ใช้ ด้วยสตริง ascii เพื่อทำงานกับภาษาอื่น ๆ

น่าเสียดายที่การใช้ถ้อยคำของข้อกำหนดของ wchar_t ถือว่าการแมปแบบหนึ่งต่อหนึ่งระหว่างอักขระและจุดรหัสเพื่อให้บรรลุสิ่งนี้ Unicode ทำลายสมมติฐานที่2ดังนั้นคุณจึงไม่สามารถใช้ wchar_t สำหรับอัลกอริทึมข้อความอย่างง่ายได้อย่างปลอดภัย

ซึ่งหมายความว่าซอฟต์แวร์พกพาไม่สามารถใช้ wchar_t เป็นตัวแทนทั่วไปสำหรับข้อความระหว่างโลแคลหรือเพื่อเปิดใช้งานการใช้อัลกอริทึมข้อความธรรมดา

วันนี้ wchar_t ใช้อะไร

ไม่มากสำหรับรหัสพกพาอยู่แล้ว ถ้า__STDC_ISO_10646__ถูกกำหนดแล้วค่าของ wchar_t จะแทนจุดรหัส Unicode โดยตรงด้วยค่าเดียวกันในทุกภาษา ทำให้ปลอดภัยในการแปลงระหว่างสถานที่ที่กล่าวถึงก่อนหน้านี้ อย่างไรก็ตามคุณไม่สามารถพึ่งพาเพียงเพื่อตัดสินใจว่าคุณสามารถใช้ wchar_t ด้วยวิธีนี้ได้เนื่องจากในขณะที่แพลตฟอร์ม unix ส่วนใหญ่กำหนด แต่ Windows ก็ไม่ได้แม้ว่า Windows จะใช้ wchar_t locale เดียวกันในทุกภาษา

เหตุผลของ Windows ไม่ได้กำหนด__STDC_ISO_10646__เป็นเพราะวินโดวส์ใช้ UTF-16 การเข้ารหัส wchar_t ของตนและเพราะ UTF-16 ใช้ตัวแทนคู่จะเป็นตัวแทน codepoints มากกว่า U + FFFF ซึ่งหมายความว่า UTF-16 __STDC_ISO_10646__ไม่ได้ตอบสนองความต้องการสำหรับ

สำหรับโค้ดเฉพาะแพลตฟอร์ม wchar_t อาจมีประโยชน์มากกว่า จำเป็นต้องใช้เป็นหลักใน Windows (เช่นไฟล์บางไฟล์ไม่สามารถเปิดได้โดยไม่ใช้ชื่อไฟล์ wchar_t) แม้ว่า Windows จะเป็นแพลตฟอร์มเดียวที่เป็นจริงเท่าที่ฉันรู้ (ดังนั้นเราอาจคิดว่า wchar_t เป็น 'Windows_char_t')

ในการมองย้อนกลับ wchar_t ไม่มีประโยชน์อย่างชัดเจนสำหรับการลดความซับซ้อนในการจัดการข้อความหรือใช้เป็นพื้นที่จัดเก็บข้อความที่ไม่ขึ้นกับโลแคล รหัสพกพาไม่ควรพยายามใช้เพื่อวัตถุประสงค์เหล่านี้ โค้ดที่ไม่สามารถพกพาได้อาจพบว่ามีประโยชน์เพียงเพราะ API บางตัวต้องการ

ทางเลือก

ทางเลือกที่ฉันชอบคือการใช้สตริง C ที่เข้ารหัส UTF-8 แม้บนแพลตฟอร์มที่ไม่เป็นมิตรกับ UTF-8

วิธีนี้เราสามารถเขียนโค้ดแบบพกพาโดยใช้การแสดงข้อความทั่วไปข้ามแพลตฟอร์มใช้ประเภทข้อมูลมาตรฐานตามวัตถุประสงค์ที่ต้องการรับการสนับสนุนภาษาสำหรับประเภทเหล่านั้น (เช่นตัวอักษรสตริงแม้ว่าจะมีเทคนิคบางอย่างที่จำเป็นเพื่อให้ใช้งานได้กับคอมไพเลอร์บางตัว) การสนับสนุนไลบรารีมาตรฐานการสนับสนุนการดีบักเกอร์ (อาจจำเป็นต้องใช้เทคนิคเพิ่มเติม) ฯลฯ ด้วยอักขระแบบกว้างโดยทั่วไปแล้วจะยากหรือเป็นไปไม่ได้ที่จะได้รับทั้งหมดนี้และคุณอาจได้รับชิ้นส่วนที่แตกต่างกันบนแพลตฟอร์มที่แตกต่างกัน

สิ่งหนึ่งที่ UTF-8 ไม่มีให้คือความสามารถในการใช้อัลกอริทึมข้อความอย่างง่ายเช่นเป็นไปได้กับ ASCII ใน UTF-8 นี้ไม่เลวร้ายไปกว่าการเข้ารหัส Unicode อื่น ๆ ในความเป็นจริงอาจถือได้ว่าดีกว่าเนื่องจากการแสดงหน่วยหลายรหัสใน UTF-8 เป็นเรื่องปกติมากขึ้นดังนั้นข้อบกพร่องในการจัดการรหัสการแสดงความกว้างตัวแปรของอักขระจึงมีแนวโน้มที่จะสังเกตเห็นและแก้ไขได้มากกว่าหากคุณพยายามยึดติดกับ UTF -32 พร้อม NFC หรือ NFKC

แพลตฟอร์มจำนวนมากใช้ UTF-8 เป็นการเข้ารหัสถ่านดั้งเดิมและหลายโปรแกรมไม่ต้องการการประมวลผลข้อความที่สำคัญใด ๆ ดังนั้นการเขียนโปรแกรมที่เป็นสากลบนแพลตฟอร์มเหล่านั้นจึงแตกต่างจากการเขียนโค้ดเล็กน้อยโดยไม่คำนึงถึงความเป็นสากล การเขียนโค้ดแบบพกพาที่แพร่หลายมากขึ้นหรือการเขียนบนแพลตฟอร์มอื่น ๆ จำเป็นต้องใส่การแปลงที่ขอบเขตของ API ที่ใช้การเข้ารหัสอื่น ๆ

อีกทางเลือกหนึ่งที่ซอฟต์แวร์บางตัวใช้คือการเลือกการแสดงข้ามแพลตฟอร์มเช่นอาร์เรย์แบบสั้นที่ไม่ได้ลงชื่อซึ่งถือข้อมูล UTF-16 จากนั้นให้การสนับสนุนไลบรารีทั้งหมดและเพียงแค่ใช้ค่าใช้จ่ายในการรองรับภาษาเป็นต้น

C ++ 11 เพิ่มอักขระแบบกว้างชนิดใหม่เป็นทางเลือกแทน wchar_t, char16_t และ char32_t พร้อมคุณสมบัติภาษา / ไลบรารีของผู้ดูแล สิ่งเหล่านี้ไม่ได้รับการรับรองว่าเป็น UTF-16 และ UTF-32 แต่ฉันไม่คิดว่าการใช้งานหลัก ๆ จะใช้อย่างอื่น C ++ 11 ยังปรับปรุงการรองรับ UTF-8 ด้วยเช่นกับ UTF-8 string literals ดังนั้นจึงไม่จำเป็นต้องหลอกให้ VC ++ สร้างสตริงที่เข้ารหัส UTF-8 (แม้ว่าฉันจะดำเนินการต่อไปแทนที่จะใช้u8คำนำหน้า) .

ทางเลือกอื่นที่ควรหลีกเลี่ยง

TCHAR: TCHAR ใช้สำหรับการย้ายโปรแกรม Windows โบราณที่ถือว่าการเข้ารหัสแบบดั้งเดิมจาก char เป็น wchar_t และจะลืมได้ดีที่สุดเว้นแต่ว่าโปรแกรมของคุณจะถูกเขียนขึ้นในพันปีก่อนหน้านี้ ไม่ใช่แบบพกพาและไม่เฉพาะเจาะจงเกี่ยวกับการเข้ารหัสและแม้แต่ประเภทข้อมูลทำให้ใช้ไม่ได้กับ API ที่ไม่ใช้ TCHAR เนื่องจากจุดประสงค์คือการย้ายข้อมูลไปที่ wchar_t ซึ่งเราได้เห็นข้างต้นไม่ใช่ความคิดที่ดีจึงไม่มีคุณค่าใด ๆ ในการใช้ TCHAR


1. อักขระที่แสดงได้ในสตริง wchar_t แต่ไม่ได้รับการสนับสนุนในโลแคลใด ๆ ไม่จำเป็นต้องแสดงด้วยค่า wchar_t เดียว ซึ่งหมายความว่า wchar_t สามารถใช้การเข้ารหัสความกว้างตัวแปรสำหรับอักขระบางตัวซึ่งเป็นการละเมิดเจตนาของ wchar_t อย่างชัดเจน แม้ว่าจะเป็นที่ถกเถียงกันอยู่ว่าอักขระที่ wchar_t แสดงได้นั้นเพียงพอที่จะบอกได้ว่าโลแคล 'รองรับ' อักขระนั้นซึ่งในกรณีนี้การเข้ารหัสความกว้างตัวแปรไม่ถูกกฎหมายและการใช้ UTF-16 ของ Window ไม่เป็นไปตามนั้น

2. Unicode ช่วยให้สามารถแสดงอักขระจำนวนมากด้วยจุดรหัสหลายจุดซึ่งจะสร้างปัญหาเดียวกันสำหรับอัลกอริทึมข้อความธรรมดาเช่นการเข้ารหัสความกว้างตัวแปร แม้ว่าจะมีการรักษามาตรฐานที่ประกอบด้วยไว้อย่างเคร่งครัด แต่อักขระบางตัวก็ยังต้องการโค้ดหลายจุด ดู: http://www.unicode.org/standard/where/


3
เพิ่มเติม: utf8everywhere.orgแนะนำให้ใช้ UTF-8 บน Windows และ Boost Nowide มีกำหนดสำหรับการตรวจสอบอย่างเป็นทางการ
Yakov Galka

2
สิ่งที่ดีที่สุดคือการใช้ C # หรือ VB.Net บน Windows :) หรือ C / Win32 แบบเก่า แต่ถ้าคุณต้องใช้ C ++ TCHAR เป็นวิธีที่ดีที่สุด ซึ่งมีค่าเริ่มต้นเป็น "wchar_t" บน MSVS2005 และสูงกว่า IMHO ...
paulsm4

4
@BrendanMcK: แน่นอนว่าไม่มีรหัสที่ใช้ Win32 API บน windows และ API อื่น ๆ ในระบบอื่น ขวา? ปัญหาเกี่ยวกับแนวทางของไมโครซอฟท์ ("ใช้ wchar ภายในทุกที่ในแอปของคุณ") คือส่งผลกระทบแม้กระทั่งรหัสที่ไม่ได้เชื่อมต่อกับระบบโดยตรงและอาจพกพาได้
Yakov Galka

4
ปัญหาคือคุณต้องใช้ฟังก์ชันเฉพาะของ Windows เนื่องจากการตัดสินใจของ Microsoft ที่จะไม่สนับสนุน UTF-8 เนื่องจากโค้ดเพจ ANSI "แบ่ง" ไลบรารี Standard C (++) ตัวอย่างเช่นคุณไม่สามารถfopenไฟล์ที่มีชื่อประกอบด้วยอักขระที่ไม่ใช่ ANSI
dan04

11
@ dan04 ใช่คุณไม่สามารถใช้ไลบรารีมาตรฐานบน Windows ได้ แต่คุณสามารถสร้างอินเทอร์เฟซแบบพกพาที่รวมไลบรารีมาตรฐานบนแพลตฟอร์มอื่น ๆ และแปลงจาก UTF-8 เป็น wchar_t ได้โดยตรงก่อนที่จะใช้ฟังก์ชัน Win32 W
bames53

20

wchar_t ไม่มีอะไร "ผิด" ปัญหาคือย้อนกลับไปใน NT 3.x วัน Microsoft ตัดสินใจว่า Unicode นั้นดี (เป็น) และใช้ Unicode เป็นอักขระ 16 บิต wchar_t ดังนั้นวรรณกรรมของ Microsoft ส่วนใหญ่จากช่วงกลางทศวรรษที่ 90 Unicode == utf16 == wchar_t

ซึ่งน่าเศร้าที่ไม่ได้เป็นอย่างนั้นเลย "อักขระแบบกว้าง" ไม่จำเป็นต้องมีขนาด 2 ไบต์ในทุกแพลตฟอร์มในทุกสถานการณ์

นี่เป็นหนึ่งในไพรเมอร์ที่ดีที่สุดสำหรับ "Unicode" (ไม่ขึ้นกับคำถามนี้โดยไม่ขึ้นกับ C ++) ฉันเคยเห็น: ฉันขอแนะนำอย่างยิ่ง :

และฉันเชื่อโดยสุจริตว่าวิธีที่ดีที่สุดในการจัดการกับ "ASCII 8 บิต" เทียบกับ "อักขระแบบกว้าง Win32" เทียบกับ "wchar_t-in-general" คือการยอมรับว่า "Windows แตกต่างกัน" ... และเขียนโค้ดตามนั้น

อิมโฮ ...

PS:

ฉันเห็นด้วยกับ jamesdlin ด้านบน:

ใน Windows คุณไม่มีทางเลือกจริงๆ API ภายในได้รับการออกแบบมาสำหรับ UCS-2 ซึ่งมีความสมเหตุสมผลในเวลานั้นเนื่องจากเป็นช่วงก่อนการเข้ารหัส UTF-8 และ UTF-16 ที่มีความยาวผันแปรได้มาตรฐาน แต่ตอนนี้พวกเขารองรับ UTF-16 แล้วพวกเขาก็ได้พบกับความเลวร้ายของทั้งสองโลก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.