C ++ 11이 유니 코드를 지원한다고 읽었습니다. 그것에 대한 몇 가지 질문 :
- C ++ 표준 라이브러리는 유니 코드를 얼마나 잘 지원합니까?
- 합니까는
std::string
무엇을해야합니까? - 어떻게 사용합니까?
- 잠재적 인 문제는 어디에 있습니까?
C ++ 11이 유니 코드를 지원한다고 읽었습니다. 그것에 대한 몇 가지 질문 :
std::string
무엇을해야합니까?답변:
C ++ 표준 라이브러리는 유니 코드를 얼마나 잘 지원합니까?
몹시.
유니 코드 지원을 제공 할 수있는 라이브러리 기능을 통한 빠른 스캔은 다음 목록을 제공합니다.
첫 번째를 제외한 모든 것이 끔찍한 지원을 제공한다고 생각합니다. 다른 질문을 빠르게 우회 한 후 더 자세히 설명하겠습니다.
합니까는
std::string
무엇을해야합니까?
예. C ++ 표준에 따르면, 이것이 std::string
형제 자매가해야 할 일입니다.
클래스 템플릿
basic_string
은 위치 0에 시퀀스의 첫 번째 요소가 포함 된 다양한 임의의 문자형 객체로 구성된 시퀀스를 저장할 수있는 객체를 설명합니다.
글쎄, std::string
괜찮아. 이것이 유니 코드 특정 기능을 제공합니까? 아니.
그래야합니까? 아마 아닙니다. std::string
일련의 char
객체 로 괜찮습니다 . 유용합니다. 유일한 성가심은 텍스트에 대한 매우 낮은 수준의 견해이며 표준 C ++은 더 높은 수준의 내용을 제공하지 않는다는 것입니다.
어떻게 사용합니까?
일련의 char
객체 로 사용하십시오 . 다른 척하는 것은 고통으로 끝날 수밖에 없다.
잠재적 인 문제는 어디에 있습니까?
여기 저기? 보자 ...
문자열 라이브러리
문자열 라이브러리는 basic_string
표준을 "char-like objects"라고 부르는 시퀀스 일뿐입니다. 코드 단위라고합니다. 높은 수준의 텍스트보기를 원한다면 이것이 원하는 것이 아닙니다. 직렬화 / 직렬화 / 저장에 적합한 텍스트보기입니다.
또한 좁은 세계와 유니 코드 세계 사이의 간극을 연결하는 데 사용할 수있는 C 라이브러리의 일부 도구 인 c16rtomb
/ mbrtoc16
및 c32rtomb
/를 제공 mbrtoc32
합니다.
현지화 라이브러리
지역화 라이브러리는 여전히 "char-like objects"중 하나가 "character"와 같다고 생각합니다. 이것은 물론 어리석은 일이며 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"
? 이러한 함수는 하나의 코드 단위 만 입력으로 사용하므로 작동 할 방법이 없습니다.
UTF-32의 단일 코드 단위 인 경우 char32_t
에만 적절한 로케일로 작업 할 수 있습니다 U'\U0001F34C'
.
그러나, 여전히 당신은 단지 간단한 케이스로 변환 얻을 것을 의미 toupper
하고 tolower
"SS"를 "ß"uppercases ☦하지만, 예를 들어, 독일의 일부 로케일에 대해 좋은 충분하지 않습니다, toupper
단지 하나 개의 반환 할 수 문자 코드 단위를.
다음은, wstring_convert
/ wbuffer_convert
표준 코드 변환면.
wstring_convert
주어진 인코딩의 문자열을 다른 주어진 인코딩의 문자열로 변환하는 데 사용됩니다. 이 변환에는 두 가지 문자열 유형이 있으며 표준은 바이트 문자열과 넓은 문자열을 호출합니다. 이러한 용어는 실제로 오해의 소지가 있으므로, 대신 "직렬화"및 "직렬화 해제"를 각각 선호합니다. †.
변환 할 인코딩은에 템플릿 유형 인수로 전달 된 codecvt (코드 변환 패싯)에 의해 결정됩니다 wstring_convert
.
wbuffer_convert
바이트 직렬화 된 스트림 버퍼 를 감싸는 넓은 역 직렬화 된 스트림 버퍼 와 유사한 기능을 수행합니다 . 모든 입출력은 codecvt 인수에 의해 주어진 인코딩과의 변환과 함께 기본 바이트 직렬 스트림 버퍼를 통해 수행됩니다 . 쓰기는 해당 버퍼로 직렬화 한 다음 버퍼에서 읽고 읽기는 버퍼로 읽은 후 직렬화 해제합니다.
표준은 이러한 시설에 사용하기위한 몇 가지 codecvt 클래스 템플릿을 제공 : codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
, 일부 codecvt
전문. 이러한 표준 패싯을 함께 사용하면 다음과 같은 변환이 모두 제공됩니다. (참고 : 다음 목록에서 왼쪽의 인코딩은 항상 직렬화 된 문자열 / 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는 기본 다국어 평면 만 지원하므로 1996 년에 대체 된 Unicode 1.0의 인코딩입니다. 위원회가 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은 0xD800-0xDFFF 범위의 값을 인코딩 할 수 없으므로 UTF-8로 변환 할 수 없습니다.
여전히 UCS-2에서 UTF-16 바이트 스트림에서 이러한 패싯이있는 UTF-16 문자열로 읽을 수있는 방법이 없습니다. UTF-16 바이트 시퀀스가 있으면 문자열을로 직렬화 해제 할 수 없습니다 char16_t
. 이것은 신원 전환이기 때문에 놀랍습니다. 그럼에도 불구하고 UTF-16 스트림에서 UCS-2 문자열로 역 직렬화를 지원한다는 사실이 더 놀랍습니다 codecvt_utf16<char16_t>
.
UTF-16-as-bytes 지원은 BOM에서 엔디안 감지 또는 코드에서 명시 적으로 선택 지원합니다. 또한 BOM이 있거나없는 결과물 생성을 지원합니다.
더 흥미로운 전환 가능성이 없습니다. UTF-8은 역 직렬화 된 형식으로 지원되지 않으므로 UTF-16 바이트 스트림 또는 문자열에서 UTF-8 문자열로 역 직렬화하는 방법은 없습니다.
그리고 여기서 좁고 넓은 세계는 UTF / UCS 세계와 완전히 분리되어 있습니다. 이전 스타일의 좁은 / 와이드 인코딩과 유니 코드 인코딩 간에는 변환이 없습니다.
입출력 라이브러리
I / O 라이브러리는 위에서 설명한 wstring_convert
및 wbuffer_convert
기능을 사용하여 유니 코드 인코딩으로 텍스트를 읽고 쓰는 데 사용할 수 있습니다 . 표준 라이브러리 의이 부분에서 지원해야 할 것이 많지 않다고 생각합니다.
정규식 라이브러리
전에 스택 오버플로에서 C ++ 정규식 및 유니 코드 관련 문제에 대해 설명했습니다 . 여기서는 모든 점을 반복하지는 않지만 C ++ 정규 표현식에는 레벨 1 유니 코드 지원이 없다고 말하면 UTF-32를 사용하지 않고도 사용할 수있는 최소한의 수준입니다.
그게 다야?
그래 그거야. 이것이 기존 기능입니다. 정규화 나 텍스트 분할 알고리즘처럼 보이지 않는 유니 코드 기능이 많이 있습니다.
U + 1F4A9 . C ++에서 더 나은 유니 코드 지원을 얻을 수있는 방법이 있습니까?
일반적인 용의자 : ICU 및 Boost.Locale .
† 바이트 문자열은 당연히 바이트 문자열, 즉 char
객체입니다. 그러나 항상 객체 배열 인 와이드 문자열 리터럴 과 달리이 wchar_t
컨텍스트에서 "와이드 문자열"은 반드시 wchar_t
오브젝트 문자열 일 필요는 없습니다 . 실제로이 표준은 "와이드 문자열"의 의미를 명시 적으로 정의하지 않으므로 사용법에서 의미를 추측해야합니다. 표준 용어는 조잡하고 혼동 스럽기 때문에 명확성을 위해 고유 한 용어를 사용합니다.
UTF-16과 같은 인코딩은의 시퀀스로 저장 될 수 있으며 char16_t
엔디안이 없습니다. 또는 엔디안을 갖는 바이트 시퀀스로 저장 될 수 있습니다 (각 연속되는 바이트 쌍은 char16_t
엔디안에 따라 다른 값을 나타낼 수 있음 ). 표준은이 두 가지 형식을 모두 지원합니다. char16_t
프로그램의 내부 조작에 시퀀스 가 더 유용합니다. 일련의 바이트는 그러한 문자열을 외부 세계와 교환하는 방법입니다. "바이트"와 "와이드"대신에 사용할 용어는 "직렬화"및 "직렬화 해제"입니다.
‡ "그러나 Windows!"라고 말하는 경우 보류 🐎🐎을 . Windows 2000 이후의 모든 Windows 버전은 UTF-16을 사용합니다.
☦ 그렇습니다. Grozes Eszett (ẞ) 에 대해 알고 있지만 밤새 ß 대문자를 사용하도록 모든 독일 로케일을 변경하더라도 여전히 실패 할 수있는 다른 경우가 많이 있습니다. 대문자로 바꾸십시오 U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ가 없습니다. 그것은 단지 두 개의 F로 대문자입니다. 또는 U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; 사전 구성된 자본이 없습니다. 그것은 대문자 J와 결합 카론에 대문자입니다.
표준 라이브러리 는 유니 코드를 지원하지 않습니다 (합리적인 의미로 지원됨).
std::string
더 좋은보다하지 std::vector<char>
가 유니 코드로 완전히 망각 (또는 다른 표현 / 인코딩) 단순히 같은 내용 치료 : BLOB 바이트를.
blob 만 저장하고 연결해야하는 경우 에는 잘 작동합니다. 그러나 유니 코드 기능 ( 코드 포인트 수, 그래프 수 등)을 원하면 운이 좋지 않습니다.
내가 아는 유일한 종합 라이브러리는 ICU 입니다. C ++ 인터페이스는 Java 인터페이스에서 파생되었으므로 관용적이지는 않습니다.
당신은 안전하게에서 UTF-8로 저장할 수 있습니다 std::string
(또는에서 char[]
또는 char*
, 그 문제에 대한) 때문에 유니 코드 NUL (+ 0000 U)는 UTF-8 널 바이트가 있다는 사실과이 유일한 방법 널입니다 바이트는 UTF-8에서 발생할 수 있습니다. 따라서 UTF-8 문자열은 모든 C 및 C ++ 문자열 함수에 따라 올바르게 종료되며 C ++ iostream ( std::cout
및 std::cerr
로케일이 UTF-8 인 경우)을 사용 하여 슬링 할 수 있습니다 .
std::string
UTF-8에서 할 수없는 것은 코드 포인트의 길이를 얻는 것입니다. UTF-8의 ASCII 하위 집합에있을 때 코드 std::string::size()
길이와 바이트 수와 같은 문자열 길이를 바이트 단위로 알려줍니다 .
코드 포인트 수준 에서 UTF-8 문자열을 처리해야하는 경우 (예 : 저장 및 인쇄가 아닌) 내부 널 바이트가 많은 UTF-16을 처리하는 경우 조사해야합니다. 넓은 문자열 유형
std::string
포함 된 null을 사용하여 iostream에 던져 넣을 수 있습니다.
c_str()
하기 때문에 전혀 깨지지 않습니다 size()
. 손상된 API (즉, 대부분의 C 세계와 같이 포함 된 null을 처리 할 수없는 API) 만 중단됩니다.
c_str()
하므로 포함 된 null이 끊어집니다. 이는 c_str()
C 문자열에 null이 포함될 수 없기 때문에 불가능합니다.
c_str()
이제는 단순히 data()
모든 것과 같은 것을 반환합니다 . 크기가 큰 API는이를 사용할 수 있습니다. 그렇지 않은 API는 할 수 없습니다.
c_str()
인해 결과에 NUL char-like 객체가 오는지 확인하지만 그렇게 생각 data()
하지 않습니다. 아니, data()
지금도 그렇게 보인다 . (물론 터미네이터 검색에서 크기를 추론하는 대신 크기를 소비하는 API에는 필요하지 않습니다.)
C ++ 11에는 유니 코드를위한 몇 가지 새로운 리터럴 문자열 유형 이 있습니다.
불행하게도 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)
있습니다.