내가 기본적으로 사용하는 언어는 C++이다. C++은 간단한 언어 같아보이지만 실제로 생각하면서 프로그래밍 해야할 부분이 굉장히 많다. 중요한 것만 꼽아봐도 적어도 다음 세가지는 들어간다.
- 우선 기본적으로 C++에서 boost::shared_ptr로 도배를 하지 않는 이상 (도배를 한다고 해도 -_-), 동적 할당된 자원 관리 문제가 있다.
- Signal이나 exception이 발생했을 때 이에 대해 안전하면서도 (자원을 놓치거나 하지 않고) 중립적인 (caller에게 제대로 전달하는) 코드를 작성하는 문제도 있다.
- Thread-safe code를 짜야한다. 설마 single thread만으로 모든 일을 해결할 수는 없을 것이다. 미리 multi-thread safe하지 않게 생각하지 않고 threaded 환경으로 넘어갔을 때 제대로 돌아가는 코드가 나오게 만드는 것은 거의 불가능하다.
그리고 요구되는 사항이 한가지 더 있다. i18n도 생각해야한다. (i18n은 internationalization이라는 길고긴 문자를 줄여서 쓴 것이다. i와 n사에 18개의 문자가 있다) 절대로 읽을 수 없는 인코딩의 e-mail을 받아본 기억은 누구에게나 존재할 것이다. 혹은 인코딩 설정에 따라서 깨지는 웹 페이지 역시. 같은 한국어라고 해도 이렇게 `어떻게 표현되는가’에 따라서 제대로 보일지 알 수가 없는데, 다른 방식을 쓰는 타국의 언어는 어떨까? (대부분의 charset에서 proper subset인 US-ascii 같은 예외를 빼면; 심지어 이놈들도 제대로 안보이게 만들어주는 방식도 있다)
우선 locale 얘기부터 해보자. C++에서는 각 국가/지역 특성에 맞춰서 문자열 / 시간 / 날짜 / 통화량에 대한 정보를 읽고 쓰기 위해 std::locale이라는 추상화를 제공한다. 예를 들어 cin에 대해 locale(“ko_KR.eucKR”) 을 사용하고 싶은 경우 해당 locale 객체를 cin.imbue (locale 객체)라는 함수 호출을 통해서, cin에서 읽어들이는 숫자/시간/날짜/정렬순서(이게 여기서 의미가 있을지는 -_-;)등을 규정하게 된다.
std::locale은 실제로 숫자/통화량/날짜/문자열 비교 (colloation)를 지원하는 여러 facet의 집합으로 이루어져 있다. (std::locale::facet)
그래서 std::locale에 해당하는 타입을 새로 정의하거나 일부 facet을 수정하여 기능을 변경할 수 있다. 실제 이 facet들은 시스템에 미리 설정된 LC_* 들에 의해서 결정된다. 예를 들어 내가 글을 쓰고 있는 이 컴퓨터는,
[rein@rein.dnip.net:~]> locale
LANG=ko_KR.UTF-8
LC_CTYPE=”ko_KR.UTF-8″
LC_NUMERIC=”ko_KR.UTF-8″
LC_TIME=”ko_KR.UTF-8″
LC_COLLATE=”ko_KR.UTF-8″
LC_MONETARY=”ko_KR.UTF-8″
LC_MESSAGES=”ko_KR.UTF-8″
LC_PAPER=”ko_KR.UTF-8″
LC_NAME=”ko_KR.UTF-8″
LC_ADDRESS=”ko_KR.UTF-8″
LC_TELEPHONE=”ko_KR.UTF-8″
LC_MEASUREMENT=”ko_KR.UTF-8″
LC_IDENTIFICATION=”ko_KR.UTF-8″
LC_ALL=ko_KR.UTF-8
이런 식으로 설정되어 있다. 문자열 (LC_CTYPE)은 한국어의 utf-8표현으로, 숫자/시간/문자열비교/메시지/통화량 표현등도 비슷하게 표현될 것이라고 생각할 수 있다. (사실 저 수많은 LC_* 중에 실제로 C++에서 사용하는 것은 처음 6개만이라고 알고있다. (C/NUMERIC/TIME/COLLATE/MONETARY/MESSAGES)
아주 간단한 예제를 들어보자. facet중에는 monetary를 다루는 facet이 있다. 여기에서는 각 locale 별 통화량 표기를 쓸 수도 있는데, 각 국가별로 몇 개 돌려보려면 다음과 같은 코드면 된다.
char* locale_list[] = { "ko_KR.utf8", "en_US.utf8", "en_GB.utf8", "de_DE.utf8", "fr_FR.utf8", "ja_JP.utf8", "zh_TW.utf8", "zh_HK.utf8" }; for(int i = 0; i < 8; ++i) { locale lc(locale_list[i]); cout << lc.name() << ": " << use_facet >(lc).curr_symbol() < < endl; } return 0;
실제로 실행해 보면 다음과 같은 결과가 나온다.
ko_KR.utf8: ₩
en_US.utf8: $
en_GB.utf8: £
de_DE.utf8: €
fr_FR.utf8: €
ja_JP.utf8: ¥
zh_TW.utf8: NT$
zh_HK.utf8: HK$
Locale 이름 뒤에 .utf8을 붙인 것은 내 터미널이 utf8으로 인코딩된 텍스트를 처리하고 있기 때문이다. (만약 utf8을 붙이지 않고 했다면 utf8으로 문자열이 날아올 거라고 믿고있던 터미널은 . ? 등등의 해괴한 문자를 보여줄 것이다) 이건 단순한 예제이고, 천단위 구분자 (thousand_seperator) 등을 붙여서 표기하면 다음과 같은 것을 볼 수 있다.