Tôi đã đọc và nghe rằng C ++ 11 hỗ trợ Unicode. Một vài câu hỏi về điều đó:
- Thư viện chuẩn C ++ hỗ trợ Unicode tốt như thế nào?
- Có
std::string
làm những gì nó nên? - Làm thế nào để tôi sử dụng nó?
- Vấn đề tiềm ẩn ở đâu?
Tôi đã đọc và nghe rằng C ++ 11 hỗ trợ Unicode. Một vài câu hỏi về điều đó:
std::string
làm những gì nó nên?Câu trả lời:
Thư viện chuẩn C ++ hỗ trợ unicode tốt như thế nào?
Kinh khủng
Quét nhanh qua các phương tiện thư viện có thể cung cấp hỗ trợ Unicode cho tôi danh sách này:
Tôi nghĩ tất cả nhưng cái đầu tiên cung cấp hỗ trợ khủng khiếp. Tôi sẽ lấy lại chi tiết hơn sau khi đi nhanh qua các câu hỏi khác của bạn.
Có
std::string
làm những gì nó nên?
Đúng. Theo tiêu chuẩn C ++, đây là những gì std::string
và anh chị em của nó nên làm:
Mẫu lớp
basic_string
mô tả các đối tượng có thể lưu trữ một chuỗi bao gồm một số lượng khác nhau các đối tượng giống như char tùy ý với phần tử đầu tiên của chuỗi ở vị trí 0.
Vâng, std::string
điều đó chỉ tốt thôi. Điều đó có cung cấp bất kỳ chức năng cụ thể nào cho Unicode không? Không.
Có nên không? Chắc là không. std::string
là tốt như một chuỗi các char
đối tượng. Điều đó hữu ích; điều khó chịu duy nhất là nó là một chế độ xem văn bản ở mức độ rất thấp và C ++ tiêu chuẩn không cung cấp mức độ cao hơn.
Làm thế nào để tôi sử dụng nó?
Sử dụng nó như một chuỗi các char
đối tượng; giả vờ nó là một cái gì đó khác chắc chắn sẽ kết thúc trong đau đớn.
Vấn đề tiềm ẩn ở đâu?
Khắp nơi? Hãy xem nào...
Thư viện chuỗi
Thư viện chuỗi cung cấp cho chúng ta basic_string
, đây chỉ là một chuỗi những gì mà tiêu chuẩn gọi là "các đối tượng giống như char". Tôi gọi họ là đơn vị mã. Nếu bạn muốn có một cái nhìn văn bản cấp cao, đây không phải là thứ bạn đang tìm kiếm. Đây là một khung nhìn của văn bản phù hợp cho việc tuần tự hóa / giải tuần tự hóa / lưu trữ.
Nó cũng cung cấp một số công cụ từ thư viện C có thể được sử dụng để thu hẹp khoảng cách giữa thế giới hẹp và thế giới Unicode: c16rtomb
/ mbrtoc16
và c32rtomb
/mbrtoc32
.
Thư viện địa phương
Thư viện bản địa hóa vẫn tin rằng một trong những "vật thể giống như char" đó bằng một "ký tự". Điều này tất nhiên là ngớ ngẩn, và làm cho không thể có nhiều thứ hoạt động chính xác ngoài một số tập hợp nhỏ của Unicode như ASCII.
Ví dụ, hãy xem xét những gì tiêu chuẩn gọi là "giao diện tiện lợi" trong <locale>
tiêu đề:
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);
// ...
Làm thế nào để bạn mong đợi bất kỳ chức năng nào trong số này để phân loại chính xác, giả sử, U + 1F34C, như trong u8"🍌"
hoặc u8"\U0001F34C"
? Không có cách nào nó sẽ hoạt động, bởi vì các hàm đó chỉ lấy một đơn vị mã làm đầu vào.
Điều này có thể hoạt động với một miền thích hợp nếu bạn char32_t
chỉ sử dụng :U'\U0001F34C'
là một đơn vị mã duy nhất trong UTF-32.
Tuy nhiên, điều đó vẫn có nghĩa là bạn chỉ nhận được các phép biến đổi vỏ đơn giản với toupper
và tolower
, ví dụ, không đủ tốt cho một số ngôn ngữ Đức: "ß" chữ hoa thành "SS" nhưng toupper
chỉ có thể trả về một đơn vị mã ký tự .
Lên tiếp theo, wstring_convert
/ wbuffer_convert
và các khía cạnh chuyển đổi mã tiêu chuẩn.
wstring_convert
được sử dụng để chuyển đổi giữa các chuỗi trong một mã hóa đã cho thành các chuỗi trong một mã hóa đã cho khác. Có hai loại chuỗi liên quan đến phép chuyển đổi này, mà tiêu chuẩn gọi là chuỗi byte và chuỗi rộng. Vì các thuật ngữ này thực sự sai lệch, tôi thích sử dụng "tuần tự hóa" và "giải tuần tự hóa", thay vào đó.
Các mã hóa để chuyển đổi giữa được quyết định bởi một codecvt (một khía cạnh chuyển đổi mã) được truyền dưới dạng đối số kiểu mẫu tới wstring_convert
.
wbuffer_convert
thực hiện một chức năng tương tự nhưng như một bộ đệm luồng khử lưu lượng rộng bao bọc một bộ đệm dòng tuần tự byte . Bất kỳ I / O nào cũng được thực hiện thông qua bộ đệm dòng tuần tự byte cơ bản với các chuyển đổi đến và từ các mã hóa được đưa ra bởi đối số codecvt. Viết tuần tự vào bộ đệm đó, sau đó viết từ nó, và đọc đọc vào bộ đệm và sau đó giải tuần tự từ nó.
Tiêu chuẩn này cung cấp một số lớp mẫu codecvt để sử dụng với các cơ sở này: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
, và một số codecvt
chuyên ngành. Các khía cạnh tiêu chuẩn này cung cấp tất cả các chuyển đổi sau đây. (Lưu ý: trong danh sách sau đây, mã hóa ở bên trái luôn là chuỗi / streambuf được tuần tự hóa và mã hóa ở bên phải luôn là chuỗi / streambuf được giải tuần tự; tiêu chuẩn cho phép chuyển đổi theo cả hai hướng).
codecvt_utf8<char16_t>
, và codecvt_utf8<wchar_t>
ở đâu sizeof(wchar_t) == 2
;codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
và codecvt_utf8<wchar_t>
nơi sizeof(wchar_t) == 4
;codecvt_utf16<char16_t>
, và codecvt_utf16<wchar_t>
ở đâu sizeof(wchar_t) == 2
;codecvt_utf16<char32_t>
, và codecvt_utf16<wchar_t>
ở đâu sizeof(wchar_t) == 4
;codecvt_utf8_utf16<char16_t>
, codecvt<char16_t, char, mbstate_t>
và codecvt_utf8_utf16<wchar_t>
nơi sizeof(wchar_t) == 2
;codecvt<wchar_t, char_t, mbstate_t>
codecvt<char, char, mbstate_t>
.Một vài trong số này là hữu ích, nhưng có rất nhiều thứ khó xử ở đây.
Trước hết hãy thay thế thánh cao cấp! kế hoạch đặt tên đó là lộn xộn.
Sau đó, có rất nhiều hỗ trợ UCS-2. UCS-2 là một mã hóa từ Unicode 1.0 được thay thế vào năm 1996 vì nó chỉ hỗ trợ mặt phẳng đa ngôn ngữ cơ bản. Tại sao ủy ban nghĩ rằng mong muốn tập trung vào một mã hóa đã được thay thế hơn 20 năm trước, tôi không biết. Nó không giống như hỗ trợ cho nhiều mã hóa là xấu hoặc bất cứ điều gì, nhưng UCS-2 xuất hiện quá thường xuyên ở đây.
Tôi muốn nói rằng điều đó char16_t
rõ ràng có nghĩa là để lưu trữ các đơn vị mã UTF-16. Tuy nhiên, đây là một phần của tiêu chuẩn nghĩ khác. codecvt_utf8<char16_t>
không có gì để làm với UTF-16. Ví dụ, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
sẽ biên dịch tốt, nhưng sẽ thất bại vô điều kiện: đầu vào sẽ được coi là chuỗi UCS-2 u"\xD83C\xDF4C"
, không thể chuyển đổi thành UTF-8 vì UTF-8 không thể mã hóa bất kỳ giá trị nào trong phạm vi 0xD800-0xDFFF.
Vẫn ở mặt trước UCS-2, không có cách nào để đọc từ luồng byte UTF-16 thành chuỗi UTF-16 với các khía cạnh này. Nếu bạn có một chuỗi các UTF-16 byte, bạn không thể giải tuần tự hóa nó thành một chuỗi char16_t
. Điều này là đáng ngạc nhiên, bởi vì nó ít nhiều là một chuyển đổi nhận dạng. Tuy nhiên, thậm chí còn đáng ngạc nhiên hơn là thực tế là có hỗ trợ khử lưu lượng từ luồng UTF-16 thành chuỗi UCS-2 với codecvt_utf16<char16_t>
, đây thực sự là một chuyển đổi mất mát.
Mặc dù vậy, hỗ trợ UTF-16-byte khá tốt: nó hỗ trợ phát hiện endianess từ BOM hoặc chọn nó một cách rõ ràng trong mã. Nó cũng hỗ trợ sản xuất đầu ra có và không có BOM.
Có một số khả năng chuyển đổi thú vị hơn vắng mặt. Không có cách nào để giải tuần tự hóa từ luồng hoặc chuỗi byte UTF-16 thành chuỗi UTF-8, vì UTF-8 không bao giờ được hỗ trợ dưới dạng khử lưu huỳnh.
Và ở đây, thế giới hẹp / rộng hoàn toàn tách biệt với thế giới UTF / UCS. Không có chuyển đổi giữa các bảng mã hẹp / rộng kiểu cũ và bất kỳ bảng mã Unicode nào.
Thư viện đầu vào / đầu ra
Thư viện I / O có thể được sử dụng để đọc và viết văn bản trong bảng mã Unicode bằng cách sử dụng wstring_convert
và wbuffer_convert
các phương tiện được mô tả ở trên. Tôi không nghĩ rằng có nhiều thứ khác cần được hỗ trợ bởi phần này của thư viện chuẩn.
Thư viện biểu thức chính quy
Tôi đã giải thích các vấn đề với biểu thức chính quy C ++ và Unicode trên Stack Overflow trước đây. Tôi sẽ không lặp lại tất cả những điểm đó ở đây, mà chỉ nói rằng các biểu thức C ++ không có hỗ trợ Unicode cấp 1, đây là mức tối thiểu để chúng có thể sử dụng được mà không cần sử dụng UTF-32 ở mọi nơi.
Đó là nó?
Vâng, đó là nó. Đó là chức năng hiện có. Có rất nhiều chức năng Unicode mà không nơi nào có thể nhìn thấy như các thuật toán phân đoạn văn bản hoặc bình thường hóa.
U + 1F4A9 . Có cách nào để có được một số hỗ trợ Unicode tốt hơn trong C ++ không?
Các nghi phạm thông thường: ICU và Boost.Locale .
Một chuỗi byte là, không có gì đáng ngạc nhiên, một chuỗi các byte, tức là char
các đối tượng. Tuy nhiên, không giống như một chuỗi ký tự rộng , luôn luôn là một mảng các wchar_t
đối tượng, một "chuỗi rộng" trong ngữ cảnh này không nhất thiết là một chuỗi các wchar_t
đối tượng. Trong thực tế, tiêu chuẩn không bao giờ định nghĩa rõ ràng "chuỗi rộng" nghĩa là gì, vì vậy chúng tôi còn lại để đoán ý nghĩa từ việc sử dụng. Vì thuật ngữ tiêu chuẩn là cẩu thả và khó hiểu, tôi sử dụng của riêng tôi, nhân danh sự rõ ràng.
Các mã hóa như UTF-16 có thể được lưu trữ dưới dạng các chuỗi char16_t
, sau đó không có tuổi thọ; hoặc chúng có thể được lưu trữ dưới dạng chuỗi byte, có tuổi thọ (mỗi cặp byte liên tiếp có thể biểu thị một char16_t
giá trị khác nhau tùy thuộc vào tuổi thọ). Tiêu chuẩn hỗ trợ cả hai hình thức này. Một chuỗi char16_t
hữu ích hơn cho thao tác nội bộ trong chương trình. Một chuỗi các byte là cách để trao đổi các chuỗi như vậy với thế giới bên ngoài. Các thuật ngữ tôi sẽ sử dụng thay vì "byte" và "wide" là "tuần tự hóa" và "giải tuần tự hóa".
Nếu bạn định nói "nhưng Windows!" giữ 🐎🐎 của bạn . Tất cả các phiên bản Windows kể từ Windows 2000 đều sử dụng UTF-16.
☦ Có, tôi biết về các Eszett (ẞ), nhưng ngay cả khi bạn thay đổi tất cả các địa phương của Đức qua đêm để có chữ hoa thành, vẫn còn nhiều trường hợp khác sẽ thất bại. Hãy thử tải lên U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ. Không có ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ғғ; nó chỉ viết hoa lên hai chữ F. Hoặc U + 01F0 sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; không có vốn đầu tư; nó chỉ viết hoa cho chữ J viết hoa và kết hợp caron.
Unicode không được Thư viện tiêu chuẩn hỗ trợ (đối với bất kỳ ý nghĩa hợp lý nào được hỗ trợ).
std::string
không tốt hơn std::vector<char>
: nó hoàn toàn không biết về Unicode (hoặc bất kỳ biểu diễn / mã hóa nào khác) và chỉ đơn giản coi nội dung của nó là một đốm byte.
Nếu bạn chỉ cần lưu trữ và catenate blobs , nó hoạt động khá tốt; nhưng ngay khi bạn muốn chức năng Unicode (số điểm mã , số biểu đồ, v.v.), bạn đã hết may mắn.
Thư viện toàn diện duy nhất tôi biết cho việc này là ICU . Giao diện C ++ được lấy từ Java, vì vậy nó không phải là thành ngữ.
Bạn có thể lưu trữ UTF-8 một cách an toàn trong một std::string
(hoặc trong một char[]
hoặc char*
, đối với vấn đề đó), do thực tế là Unicode NUL (U + 0000) là một byte rỗng trong UTF-8 và đây là cách duy nhất là null byte có thể xảy ra trong UTF-8. Do đó, các chuỗi UTF-8 của bạn sẽ được kết thúc đúng theo tất cả các hàm chuỗi C và C ++ và bạn có thể kết nối chúng với các iostream C ++ (bao gồm std::cout
và std::cerr
, miễn là ngôn ngữ của bạn là UTF-8).
Những gì bạn không thể làm với std::string
UTF-8 là lấy độ dài của các điểm mã. std::string::size()
sẽ cho bạn biết độ dài chuỗi tính bằng byte , chỉ bằng số điểm mã khi bạn nằm trong tập hợp con ASCII của UTF-8.
Nếu bạn cần hoạt động trên các chuỗi UTF-8 ở cấp điểm mã (nghĩa là không chỉ lưu trữ và in chúng) hoặc nếu bạn đang xử lý UTF-16, có khả năng có nhiều byte rỗng bên trong, bạn cần xem xét các loại chuỗi ký tự rộng.
std::string
có thể được ném vào iostream với các null được nhúng tốt.
c_str()
vì size()
vẫn hoạt động. Chỉ các API bị hỏng (tức là các API không thể xử lý các null được nhúng như hầu hết thế giới C) mới bị phá vỡ.
c_str()
vì c_str()
được cho là trả về dữ liệu dưới dạng chuỗi C kết thúc null --- điều này là không thể, do thực tế là các chuỗi C không thể nhúng null.
c_str()
bây giờ chỉ đơn giản là trả về giống như data()
, tức là tất cả của nó. API có kích thước có thể tiêu thụ nó. API không, không thể.
c_str()
đảm bảo kết quả được theo sau bởi một đối tượng giống như NUL và tôi không nghĩ data()
vậy. Không, có vẻ như data()
bây giờ cũng làm điều đó. (Tất nhiên, điều này là không cần thiết đối với các API tiêu thụ kích thước thay vì suy ra từ tìm kiếm của kẻ hủy diệt)
C ++ 11 có một vài loại chuỗi ký tự mới cho Unicode.
Thật không may, sự hỗ trợ trong thư viện tiêu chuẩn cho các bảng mã không đồng nhất (như UTF-8) vẫn còn tệ. Ví dụ, không có cách nào hay để lấy độ dài (tính theo điểm mã) của chuỗi UTF-8.
std::string
có thể giữ chuỗi UTF-8 mà không gặp vấn đề gì, nhưng ví dụ length
phương thức trả về số byte trong chuỗi chứ không phải số điểm mã.
ñ
là 'LATIN SMALL LETTER N VỚI TILDE' (U + 00F1) (là một điểm mã) hoặc 'LATIN SMALL LALLTER N' ( U + 006E) theo sau là 'COMBINING TILDE' (U + 0303) là hai điểm mã.
LATIN SMALL LETTER N'
== hay không (U+006E) followed by 'COMBINING TILDE' (U+0303)
.
Tuy nhiên, có một thư viện khá hữu ích được gọi là tiny-utf8 , về cơ bản là một thay thế thả vào cho std::string
/ std::wstring
. Nó nhằm mục đích lấp đầy khoảng trống của lớp container utf8 vẫn còn thiếu.
Đây có thể là cách 'giao dịch' thoải mái nhất với các chuỗi utf8 (nghĩa là không có chuẩn hóa unicode và các công cụ tương tự). Bạn thoải mái hoạt động trên các điểm mã , trong khi chuỗi của bạn được mã hóa theo char
s được mã hóa theo chiều dài .