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ì?


86

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 đối việc sử dụng wstringswchar_tvà việc sử dụng chúng trong api windows. Chính xác là "sai" với điều gì wchar_twstringvà nếu tôi muốn hỗ trợ quốc tế hóa, một số lựa chọn thay thế cho các ký tự rộng là gì?


1
Có bất kỳ tài liệu tham khảo cho điều đó?
Dani

14
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

15
Trên Windows, bạn không thực sự có sự lựa chọn. Các API nội bộ của nó được thiết kế cho UCS-2, điều này là hợp lý vào thời điểm đó vì nó có trước khi các mã hóa UTF-8 và UTF-16 có độ dài 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

12
utf8everywhere.org có một cuộc thảo luận tốt về các lý do để tránh các ký tự rộng.
JoeG

5
@jamesdlin Chắc chắn bạn có quyền lựa chọn. thư viện nowide cung cấp một cách thuận tiện để chuyển đổi các chuỗi chỉ khi chuyển đến các API. Các lệnh gọi API với chuỗi thường có tần suất thấp, vì vậy cách hợp lý là chuyển đổi ad-hok và luôn có các tệp và biến nội bộ trong UTF-8.
Pavel Radzivilovsky

Câu trả lời:


114

Wchar_t là gì?

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

Kiểu wchar_t là một kiểu riêng biệt có giá trị có thể đại diện cho 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 số các ngôn ngữ được hỗ trợ (22.3.1).

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

Điều này không yêu cầu wchar_t phải đủ lớn để đại diện đồng thời cho bất kỳ ký tự nào từ tất cả các ngôn ngữ. Đó 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 là bạn không thể nhất thiết phải chuyển đổi một chuỗi thành wchar_t bằng một ngôn ngữ và sau đó chuyển đổi lại thành char bằng một ngôn ngữ khác. 1

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

Mục đích và mục đích ban đầu của wchar_t là làm cho việc xử lý văn bản trở nên đơn giản bằng cách xác định nó sao cho nó yêu cầu á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, từ ngữ của đặc tả của wchar_t giả sử ánh xạ một-một giữa các ký tự và điểm mã để đạt được điều này. Unicode phá vỡ giả định 2 đó , vì vậy bạn cũng không thể sử dụng wchar_t một cách an toàn cho các thuật toán văn bản đơn giản.

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

Ngày nay wchar_t sử dụng gì?

Không nhiều, dù sao thì đối với mã di động. Nếu __STDC_ISO_10646__được định nghĩa thì các giá trị của wchar_t đại diện trực tiếp cho 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 việc chuyển đổi giữa các ngôn ngữ được đề cập trước đó trở nên an toàn. 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 định nghĩa 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 và vì UTF-16 sử dụng các cặp thay thế để biểu thị 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 __STDC_ISO_10646__.

Đối với mã nền tảng cụ thể, wchar_t có thể hữu ích hơn. Về cơ bản, nó bắt buộc phải có trên Windows (ví dụ: một số tệp không thể mở được mà không sử dụng tên tệp wchar_t), mặc dù Windows là nền tảng duy nhất mà điều này đúng theo như tôi biết (vì vậy có thể chúng ta có thể coi wchar_t là 'Windows_char_t').

Nhìn nhận lại, 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àm bộ lưu trữ cho văn bản độc lập với ngôn ngữ. Mã di động không nên cố gắng sử dụng nó cho những mục đích này. Mã không di động có thể thấy nó hữu ích đơn giản vì một số API yêu cầu nó.

Giải pháp thay thế

Giải pháp thay thế tôi thích là sử dụng chuỗi C được 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 chung trên các nền tảng, sử dụng các kiểu dữ liệu tiêu chuẩn cho mục đích dự định của họ, nhận được sự hỗ trợ của ngôn ngữ cho các kiểu đó (ví dụ: chuỗi ký tự, mặc dù một số thủ thuật là cần thiết để làm cho nó hoạt động đối với một số trình biên dịch), một số hỗ trợ thư viện tiêu chuẩn, hỗ trợ trình gỡ lỗi (có thể cần nhiều thủ thuật hơn), v.v. Với các ký tự rộng, nói chung khó hoặc không thể có được tất cả những đ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 mà 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ư khả thi với ASCII. Trong UTF-8 này không tệ hơn bất kỳ bảng mã Unicode nào khác. Trên thực tế, nó có thể được coi là tốt hơn vì các biểu diễn đơn vị đa mã trong UTF-8 phổ biến hơn và do đó các lỗi trong việc xử lý mã như các biểu diễn độ rộng thay đổi của các ký tự có nhiều khả năng được chú ý và sửa hơn là nếu bạn cố gắng bám vào UTF -32 với NFC hoặc NFKC.

Nhiều nền tảng sử dụng UTF-8 làm mã hóa ký tự gốc của chúng và nhiều chương trình không yêu cầu bất kỳ xử lý văn bản quan trọng nào và vì vậy việc viết một chương trình quốc tế hóa trên các nền tảng đó hơi khác so với việc viết mã mà không tính đến việc quốc tế hóa. Viết mã di động rộng rãi 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 ở ranh giới của các API sử dụng các mã hóa khác.

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

C ++ 11 bổ sung thêm các loại ký tự rộng mới làm lựa chọn thay thế cho wchar_t, char16_t và char32_t với các tính năng ngôn ngữ / thư viện tiếp viên. Chúng thực sự không được đảm bảo 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 hỗ trợ UTF-8, chẳng hạn với các ký tự chuỗi UTF-8, vì vậy không cần thiết phải lừa VC ++ tạo ra các chuỗi được 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 u8tiền tố) .

Các lựa chọn thay thế cần tránh

TCHAR: TCHAR dành cho việc di chuyển các chương trình Windows cổ đại giả sử các mã hóa kế thừa từ char sang wchar_t và tốt nhất là nên quên trừ khi chương trình của bạn được viết trong một thiên niên kỷ trước. Nó không di động và vốn dĩ không cụ thể về mã hóa và thậm chí là kiểu dữ liệu của nó, khiến 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 sang 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ự có thể biểu diễn trong 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 là wchar_t có thể sử dụng mã hóa độ rộng thay đổi cho các ký tự nhất định, một vi phạm rõ ràng khác về ý định của wchar_t. Mặc dù có thể cho rằng một ký tự được đại diện bởi wchar_t là đủ để nói rằng ngôn ngữ 'hỗ trợ' ký tự đó, trong trường hợp này, các mã hóa có độ rộng thay đổi là 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ã, điều này tạo ra các vấn đề tương tự đối với các thuật toán văn bản đơn giản như mã hóa độ rộng thay đổi. Ngay cả khi người ta duy trì nghiêm ngặt việc chuẩn hóa đã soạn, một số ký tự vẫn yêu cầu nhiều điểm mã. Xem: http://www.unicode.org/standard/where/


3
Bổ sung: utf8everywhere.org khuyến nghị sử dụng UTF-8 trên Windows và Boost.Nowide được lên lịch để đánh giá chính thức.
Yakov Galka

2
Tất nhiên, điều tốt nhất là sử dụng C # hoặc VB.Net trên Windows :) Hoặc C / Win32 cũ. Nhưng nếu bạn phải sử dụng C ++, thì TCHAR là cách tốt nhất để sử dụng. Mặc định là "wchar_t" trên MSVS2005 trở lên. IMHO ...
paulsm4

4
@BrendanMcK: Chắc chắn rồi, mã sử dụng Win32 API trên windows và các API khác trên các hệ thống khác không tồn tại. Đúng? Vấn đề với cách tiếp cận của microsoft ("sử dụng wchar nội bộ ở 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à thể di động.
Yakov Galka

4
Vấn đề là bạn phải sử dụng các chức năng 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 C (++) Chuẩn. Ví dụ: bạn không thể fopentệp có tên chứa các ký tự không phải ANSI.
dan04

11
@ dan04 Có, bạn không thể sử dụng thư viện tiêu chuẩn trên Windows, nhưng bạn có thể tạo giao diện di động bao bọc thư viện tiêu chuẩn trên các nền tảng khác và chuyển đổi trực tiếp từ UTF-8 sang wchar_t trước khi sử dụng các chức năng của Win32 W.
bames53

20

Không có gì "sai" với wchar_t. Vấn đề là, vào những ngày trước NT 3.x, Microsoft đã quyết định rằng Unicode là Tốt (đúng là như vậy) và triển khai Unicode dưới dạng các ký tự wchar_t 16-bit. Vì vậy, hầu hết các tài liệu của Microsoft từ giữa những năm 90 đều đánh đồng khá nhiều Unicode == utf16 == wchar_t.

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

Đây là một trong những loại sơn lót tốt nhất về "Unicode" (không phụ thuộc vào câu hỏi này, không phụ thuộc vào C ++) mà tôi từng thấy: Tôi đánh giá cao đề nghị nó:

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

IMHO ...

Tái bút:

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

Trên Windows, bạn không thực sự có sự lựa chọn. Các API nội bộ của nó được thiết kế cho UCS-2, điều này là hợp lý vào thời điểm đó vì nó có trước khi các mã hóa UTF-8 và UTF-16 có độ dài 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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.