Câu hỏi Có gì sai với C ++ wchar_t và wstrings? Một số lựa chọn thay thế cho các ký tự rộng là gì?


Tôi đã thấy rất nhiều người trong cộng đồng C ++ (đặc biệt là ## c ++ trên freenode) phẫn nộ việc sử dụng wstrings và wchar_tvà sử dụng chúng trong cửa sổ api. Chính xác là "sai" với wchar_t và wstringvà nếu tôi muốn hỗ trợ quốc tế hóa, một số lựa chọn thay thế cho nhân vật rộng là gì?


76
2018-06-19 19:00


gốc


Có bất kỳ tài liệu tham khảo cho điều đó? - Dani
Có lẽ chủ đề tuyệt vời này sẽ trả lời tất cả các câu hỏi của bạn? stackoverflow.com/questions/402283/stdwstring-vs-stdstring - MrFox
Trên Windows, bạn thực sự không có lựa chọn nào. API nội bộ của nó được thiết kế cho UCS-2, vốn đã hợp lý vào thời điểm đó vì trước khi mã hóa UTF-8 và UTF-16 thay đổi được chuẩn hóa. Nhưng bây giờ họ hỗ trợ UTF-16, họ đã kết thúc với điều tồi tệ nhất của cả hai thế giới. - jamesdlin
utf8everywhere.org có một cuộc thảo luận tốt về lý do để tránh các ký tự rộng. - Joe Gauterin
@jamesdlin Chắc chắn bạn có một sự lựa chọn. thư viện nowide cung cấp một cách thuận tiện để chuyển đổi chuỗi chỉ khi chuyển đến các API. Các cuộc gọi API có chuỗi thường là tần số thấp, do đó, cách hợp lý là chuyển đổi quảng cáo-hok và có các tệp và biến nội bộ trong UTF-8 mọi lúc. - Pavel Radzivilovsky


Các câu trả lời:


Wchar_t là gì?

wchar_t được định nghĩa sao cho mã hóa char của bất kỳ locale nào có thể được chuyển đổi thành một biểu diễn wchar_t trong đó mỗi wchar_t biểu diễn chính xác một điểm mã hóa:

Loại wchar_t là một kiểu riêng biệt có các giá trị có thể biểu diễn các mã riêng biệt cho tất cả các thành viên của tập ký tự mở rộng lớn nhất được chỉ định trong các ngôn ngữ được hỗ trợ (22.3.1).

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

Điều này không làm yêu cầu wchar_t đủ lớn để đại diện cho bất kỳ ký tự nào từ tất cả các miền cùng một lúc. Tức là, mã hóa được sử dụng cho wchar_t có thể khác nhau giữa các ngôn ngữ. Điều đó có nghĩa rằng bạn không nhất thiết phải chuyển đổi một chuỗi thành wchar_t bằng một miền địa phương và sau đó chuyển đổi lại thành char bằng cách sử dụng một ngôn ngữ khác.1

Kể từ khi sử dụng wchar_t như là một đại diện chung giữa tất cả các miền địa phương có vẻ là sử dụng chính cho wchar_t trong thực tế bạn có thể tự hỏi những gì nó tốt cho nếu không phải là.

Mục đích và mục đích ban đầu của wchar_t là xử lý văn bản đơn giản bằng cách định nghĩa nó sao cho nó đòi hỏi một ánh xạ một-một từ các đơn vị mã của chuỗi đến các ký tự của văn bản, do đó cho phép sử dụng các thuật toán đơn giản giống như được sử dụng với chuỗi ascii để làm việc với các ngôn ngữ khác.

Thật không may là từ ngữ của đặc tả của wchar_t giả định một ánh xạ một-một giữa các ký tự và các điểm mã để đạt được điều này. Unicode phá vỡ giả thiết đó2, vì vậy bạn không thể sử dụng một cách an toàn wchar_t cho các thuật toán văn bản đơn giản.

Điều này có nghĩa rằng phần mềm di động không thể sử dụng wchar_t hoặc là một đại diện chung cho văn bản giữa các miền địa phương, hoặc để cho phép sử dụng các thuật toán văn bản đơn giản.

Điều gì đang sử dụng là wchar_t ngày hôm nay?

Không nhiều, cho mã di động anyway. Nếu __STDC_ISO_10646__ được định nghĩa sau đó các giá trị của wchar_t biểu diễn trực tiếp các điểm mã Unicode với các giá trị giống nhau trong tất cả các ngôn ngữ. Điều đó làm cho nó an toàn để thực hiện chuyển đổi giữa các miền được đề cập trước đó. Tuy nhiên bạn không thể chỉ dựa vào nó để quyết định rằng bạn có thể sử dụng wchar_t theo cách này bởi vì, trong khi hầu hết các nền tảng Unix xác định nó, Windows không mặc dù Windows sử dụng cùng một ngôn ngữ wchar_t trong tất cả các ngôn ngữ.

Lý do Windows không xác định __STDC_ISO_10646__ là vì Windows sử dụng UTF-16 làm mã hóa wchar_t của nó, và vì UTF-16 sử dụng cặp thay thế để biểu diễn các điểm mã lớn hơn U + FFFF, có nghĩa là UTF-16 không đáp ứng các yêu cầu cho __STDC_ISO_10646__.

Đối với mã nền tảng cụ thể wchar_t có thể hữu ích hơn. Đó là cơ bản cần thiết trên Windows (ví dụ, một số tập tin chỉ đơn giản là không thể mở mà không sử dụng tên tập tin wchar_t), mặc dù Windows là nền tảng duy nhất mà điều này là đúng như xa như tôi biết (vì vậy có lẽ chúng ta có thể nghĩ về wchar_t là 'Windows_char_t').

Trong hindsight wchar_t rõ ràng không hữu ích cho việc đơn giản hóa việc xử lý văn bản, hoặc là lưu trữ cho văn bản độc lập miền địa phương. Mã di động không nên cố sử dụng nó cho các mục đích này. Mã không di động có thể thấy nó hữu ích đơn giản chỉ vì một số API yêu cầu nó.

Giải pháp thay thế

Cách thay thế tôi thích là sử dụng các chuỗi mã hóa UTF-8, ngay cả trên các nền tảng không đặc biệt thân thiện với UTF-8.

Bằng cách này, người ta có thể viết mã di động bằng cách sử dụng biểu diễn văn bản phổ biến trên nền tảng, sử dụng kiểu dữ liệu chuẩn cho mục đích của chúng, nhận hỗ trợ ngôn ngữ cho các loại đó (ví dụ: chuỗi ký tự, mặc dù một số thủ thuật cần thiết để làm cho nó hoạt động đối với một số trình biên dịch) Hỗ trợ thư viện chuẩn, hỗ trợ trình gỡ lỗi (nhiều thủ thuật hơn có thể cần thiết), v.v. Với các ký tự rộng, thường khó hoặc không thể có được tất cả điều này và bạn có thể nhận được các phần khác nhau trên các nền tảng khác nhau.

Một điều UTF-8 không cung cấp là khả năng sử dụng các thuật toán văn bản đơn giản như có thể với ASCII. Trong UTF-8 này không tệ hơn bất kỳ mã hóa Unicode nào khác. Trong thực tế, nó có thể được coi là tốt hơn vì biểu diễn đơn vị nhiều mã trong UTF-8 phổ biến hơn và do đó lỗi trong mã xử lý các biểu diễn độ rộng biến khác nhau của ký tự có nhiều khả năng được chú ý và cố định hơn nếu bạn cố gắng gắn với UTF -32 với NFC hoặc NFKC.

Nhiều nền tảng sử dụng UTF-8 làm mã hóa bản địa của chúng và nhiều chương trình không yêu cầu xử lý văn bản quan trọng, và do đó viết một chương trình quốc tế trên các nền tảng này hơi khác so với viết mã mà không xem xét quốc tế hóa. Viết mã di động rộng hơn hoặc viết trên các nền tảng khác yêu cầu chèn chuyển đổi ở các ranh giới của API sử dụng các mã hóa khác.

Một lựa chọn khác được sử dụng bởi một số phần mềm là chọn một biểu diễn đa nền tảng, chẳng hạn như các mảng ngắn chưa được ký chứa dữ liệu UTF-16, và sau đó cung cấp tất cả các hỗ trợ thư viện và chỉ đơn giản là sống với các chi phí hỗ trợ ngôn ngữ, v.v.

C ++ 11 thêm các loại ký tự rộng mới như các lựa chọn thay thế cho wchar_t, char16_t và char32_t với các tính năng thư viện / ngôn ngữ của người tiếp viên. Những điều này không thực sự được bảo đảm là UTF-16 và UTF-32, nhưng tôi không tưởng tượng bất kỳ triển khai chính nào sẽ sử dụng bất kỳ thứ gì khác. C ++ 11 cũng cải thiện sự hỗ trợ UTF-8, ví dụ với các chuỗi ký tự UTF-8 vì vậy sẽ không cần thiết phải lừa VC ++ để tạo ra các chuỗi mã hóa UTF-8 (mặc dù tôi có thể tiếp tục làm như vậy thay vì sử dụng u8 tiếp đầu ngữ).

Các giải pháp thay thế cần tránh

TCHAR: TCHAR là để di chuyển các chương trình Windows cổ đại mà giả sử mã hóa di sản từ char để wchar_t, và là tốt nhất quên trừ khi chương trình của bạn đã được viết trong một số thiên niên kỷ trước. Nó không phải là di động và vốn đã không xác định về mã hóa của nó và thậm chí là kiểu dữ liệu của nó, làm cho nó không thể sử dụng được với bất kỳ API không dựa trên TCHAR nào. Vì mục đích của nó là di chuyển đến wchar_t, mà chúng ta đã thấy ở trên không phải là một ý tưởng hay, nên không có giá trị gì khi sử dụng TCHAR.


1. Các ký tự đại diện trong các chuỗi wchar_t nhưng không được hỗ trợ trong bất kỳ ngôn ngữ nào không bắt buộc phải được biểu diễn bằng một giá trị wchar_t duy nhất. Điều này có nghĩa rằng wchar_t có thể sử dụng một mã hóa chiều rộng biến cho các ký tự nhất định, một vi phạm rõ ràng về ý định của wchar_t. Mặc dù có thể cho rằng một ký tự đại diện bởi wchar_t là đủ để nói rằng miền địa phương 'hỗ trợ' ký tự đó, trong trường hợp mã hóa chiều rộng biến không hợp pháp và việc sử dụng UTF-16 của Window là không phù hợp.

2. Unicode cho phép nhiều ký tự được biểu diễn với nhiều điểm mã, tạo ra cùng một vấn đề cho các thuật toán văn bản đơn giản dưới dạng mã hóa độ rộng biến đổi. Ngay cả khi một người duy trì nghiêm ngặt việc chuẩn hóa sáng tác, một số ký tự vẫn yêu cầu nhiều điểm mã. Xem: http://www.unicode.org/standard/where/


106
2018-06-25 21:52



Thêm vào: utf8everywhere.org đề xuất sử dụng UTF-8 trên Windows và Boost.Nowide được lên lịch để xem xét chính thức. - ybungalobill
@ paulsm4: may mắn chuyển mã của bạn. - ybungalobill
@BrendanMcK: Chắc chắn, mã sử dụng API Win32 trên các cửa sổ và các API khác trên các hệ thống khác không tồn tại. Đúng? Các vấn đề với cách tiếp cận của microsoft ("sử dụng wchar trong nội bộ ở khắp mọi nơi trong ứng dụng của bạn") là ảnh hưởng đến ngay cả mã không giao diện trực tiếp với hệ thống và có thể di động. - ybungalobill
Vấn đề là bạn có sử dụng các hàm dành riêng cho Windows vì quyết định của Microsoft không hỗ trợ UTF-8 như một trang mã ANSI "phá vỡ" Thư viện chuẩn C (++). Ví dụ, bạn không thể fopen một tệp có tên chứa các ký tự không phải ANSI. - dan04
@ dan04 Có, bạn không thể sử dụng thư viện chuẩn trên Windows, nhưng bạn có thể tạo một giao diện di động bao bọc thư viện chuẩn trên các nền tảng khác và chuyển đổi từ UTF-8 thành wchar_t trực tiếp trước khi sử dụng các hàm Win32 W. - bames53


Không có gì "sai" với wchar_t. Vấn đề là, trở lại trong NT 3.x ngày, Microsoft đã quyết định rằng Unicode là tốt (nó là), và để thực hiện Unicode như 16-bit, ký tự wchar_t. Vì vậy, hầu hết các tài liệu của Microsoft từ trung bình khá nhiều, tương đương với Unicode == utf16 == wchar_t.

Mà, thật đáng buồn, không phải là ở tất cả các trường hợp. "Ký tự rộng" là không phải nhất thiết phải là 2 byte, trên tất cả các nền tảng, trong mọi trường hợp.

Đây là một trong những mồi tốt nhất về "Unicode" (độc lập với câu hỏi này, độc lập với C ++) mà tôi từng thấy: cao giới thiệu nó:

Và tôi thành thật tin rằng cách tốt nhất để đối phó với "8-bit ASCII" vs "Win32 ký tự rộng" so với "wchar_t-in-general" chỉ đơn giản là chấp nhận rằng "Windows là khác" ... và mã cho phù hợp.

IMHO ...

PS:

Tôi hoàn toàn đồng ý với jamesdlin ở trên:

Trên Windows, bạn thực sự không có lựa chọn nào. API nội bộ của nó là   được thiết kế cho UCS-2, đã hợp lý vào thời điểm đó vì nó   trước khi mã hóa UTF-8 và UTF-16 có độ dài thay đổi là   chuẩn hóa. Nhưng bây giờ họ hỗ trợ UTF-16, họ đã kết thúc với   tồi tệ nhất của cả hai thế giới.


16
2018-06-19 23:39





Bắt buộc đọc:

Tối thiểu tuyệt đối mọi nhà phát triển phần mềm hoàn toàn, tích cực phải biết về Unicode và bộ ký tự (không có lý do gì!)

Nếu bạn lập trình bằng Java hoặc .Net (VB.Net hoặc C #) - nó phần lớn là không vấn đề: cả hai đều là Unicode theo mặc định. Nếu bạn lập trình trong API Win32 "cổ điển", đặt cược tốt nhất của bạn có thể là sử dụng các macro TCHAR và _T () (chứ không phải là sử dụng explicitly wchar).

Tất cả các trình biên dịch của Microsoft VS2005 và sau này, tôi tin rằng, mặc định là 16-bit cho C / C ++ anyway (một phần lý do tôi vẫn sử dụng MSVS 6.0 bất cứ khi nào tôi có thể;)).

Một liên kết tốt khác (mặc dù hơi ngày):


-4



Tôi không nghĩ rằng đây không phải là vấn đề trong Java / .NET đơn giản vì cả hai đều là "Unicode theo mặc định". Đó là vì các ngôn ngữ chỉ định mã hóa nên mọi người sử dụng nó một cách nhất quán. Ngoài ra tôi không đồng ý rằng việc sử dụng TCHAR là một ý tưởng hay trong các chương trình hiện đại. TCHAR là để di chuyển các chương trình cổ đại từ char sang wchar_t. - bames53
Câu trả lời đơn giản là "sử dụng ASCII" hoặc "sử dụng Unicode". Mọi thứ khác là "tiếng ồn". Và, như nó hay không, "wchar_t" về cơ bản == Unicode trong Win32 C ++. Đối với Windows (và chỉ có Windows), tôi khuyên bạn nên sử dụng TCHAR thay vì wchar_t thô cho văn bản chung. Tôi cũng khuyên bạn nên sử dụng std :: string over char / wchar array. Và, thẳng thắn, tôi khuyên bạn nên sử dụng Java, C #, C hoặc chỉ là về bất cứ điều gì trên C ++. IMHO ... - paulsm4
"sử dụng Unicode" là không đủ mặc dù. Bạn phải chọn giữa các bảng mã Unicode, bạn phải chọn kiểu dữ liệu, v.v. - bames53
paulsm4: Unicode có nhiều mã hóa khác nhau, nó không đơn giản như bạn muốn. Trong thực tế, UTF-8 tương thích ngược với ascii (không giống như các mã hóa khác) nên lựa chọn cứng "ASCII" hoặc "Unicode" này không phải là vấn đề. - Inverse
@Inverse "UTF-8 tương thích ngược với ascii"hay không, tùy thuộc vào cách bạn xác định" tương thích ngược ". - curiousguy