36-2.string

36-2-가.문자열 클래스

C/C++은 언어 차원에서 문자열 타입을 제공하지 않기 때문에 다소 불편한 면이 있다. 대신 문자형 배열로 문자열을 표현하는데 여러 모로 귀찮은 일들이 많고 때로는 미리 정한 배열 크기를 벗어나면 위험해지기도 한다. 그러나 언어 차원에서 제공하는 문자열 타입이 없을 뿐이지 이를 보완할 수 있는 클래스라는 막강한 도구가 있다. C++은 문자열 타입을 제공하지 않는 대신 생성자, 파괴자, 연산자 오버로딩 등의 문법적 장치를 제공하며 이를 활용하면 베이직같은 고급언어의 문자열을 만들 수 있고 오히려 구현의 자유가 보장되므로 훨씬 더 성능이 좋고 목적에 맞는 문자열을 만들어 쓸 수 있다.

앞에서 우리는 Str이라는 이름으로 문자열 클래스를 이미 만들어 본 바 있는데 이 클래스만 해도 고급언어의 문자열 타입 흉내를 그럴듯하게 낸다. 하지만 실습용 클래스라 기능이 충분하지 않고 범용성도 떨어지는 것이 사실이다. 누군가가 문자열 클래스를 잘 만들어서 공개한다면 많은 개발자들이 이 클래스를 별다른 노력없이 재사용할 수 있을 것이다. 아마 여러분들도 이미 만들어진 문자열 클래스가 있지 않을까 찾을 것이다. 이 책에서 앞장에서 만들었던 Str이라는 클래스도 있기는 하지만 왠지 예제라 못미더워 보인다. 물론 이보다 훨씬 더 잘 만들어진 재사용 가능한 문자열 클래스들이 많이 있다. 많은 사람들이 바라는 것은 항상 현실로 나타나는 법이다.

비주얼 C++의 기본 라이브러리인 MFC에는 CString이라는 멋진 클래스가 있고 공개 자료실을 뒤져 보면 쓸만한 클래스들이 널려 있다. 그러나 이런 것들은 기능상의 문제는 없지만 언어 차원의 표준이 아니므로 아무래도 호환성이 떨어진다는 단점이 있다. 여기서 소개하는 string 클래스는 우리가 그토록 바라는 표준 문자열 클래스이다. 14882 표준에서 정의하는 C++ 표준 라이브러리의 일부이므로 표준을 준수하는 모든 컴파일러가 이 클래스를 제공한다. 따라서 호환성, 이식성 등을 걱정할 필요없이 언제나 사용 가능하다.

string 클래스는 string 헤더 파일에 정의되어 있으며 std 네임 스페이스에 포함되어 있다. 그래서 이 클래스를 쓰고 싶다면 string 헤더(string.h가 아님을 유의하자)를 인클루드하고 std 네임 스페이스에 대해 using 지시자를 사용해야 한다. string은 템플릿 기반의 클래스이므로 핵심 코드들은 거의 대부분 헤더 파일에 작성되어 있으며 이 헤더를 열어 보면 소스를 직접 볼 수 있다. 헤더 파일에는 다음과 같은 basic_string이라는 클래스 템플릿을 정의하는데 선언문은 다음과 같다.

 

template<class _Elem, class _Traits = char_traits<_Elem>, class _Ax = allocator<_Elem> >

class basic_string { 멤버 목록 };

 

선언문이 다소 복잡한데 템플릿의 인수가 세 개나 되며 인수들을 조합하는 방식에 따라 다양한 형태의 문자열 클래스를 만들 수 있다. 뒤쪽 두 개의 인수에 대해서는 디폴트가 적용되는데 대부분의 경우 디폴트가 사용되지만 원하면 변경할 수 있다. 앞쪽 두 인수의 의미는 basic_ostream의 경우와 같으므로 마지막 인수 _Ax에 대해서만 알아보자.

문자열 클래스는 가변 길이를 다룰 수 있어야 하므로 본질적으로 메모리를 동적 할당해야 한다. _Ax 인수는 문자열 관리를 위한 메모리를 어떻게 할당하고 해제할 것인가를 지정하는 할당기이다. 디폴트인 allocator<_Elem>은 C++의 할당 연산자인 new, delete를 사용하는데 원한다면 다른 것으로 바꿀 수 있다. malloc, free를 쓰는 방식도 가능하며 초대용량의 메모리를 관리해야 한다면 Win32의 가상 메모리를 직접 할당하는 방식을 쓸 수도 있다. 또는 문자열의 길이가 빈번히 변한다면 미리 다량의 메모리를 확보하여 재할당 회수를 줄이는 최적화된 방법을 구사할 수도 있다.

뒤쪽의 두 인수는 디폴트 객체가 지정되어 있으며 이 객체들도 C++ 표준 라이브러리에 의해 이미 구현되어 있으므로 생략 가능하다. 생략시 무난한 디폴트 객체가 선택되는데 이때 만들어지는 클래스는 new, delete로 할당되는 널종료 문자열이다. 이 포맷이 가장 일반적이므로 대개의 경우 디폴트만 사용해도 무난하다.

basic_string 템플릿은 충분한 확장성과 일반성을 고려하여 작성되어 있다. 운영 환경이 바뀌거나 문자열의 정의가 달라지더라도 적절한 문자열 클래스를 생성할 수 있도록 설계되어 있을 뿐이지 세 인수를 반드시 지정해야만 하는 것은 아니다. 즉, 왠만하면 디폴트를 쓰되 꼭 바꾸고 싶다면 원하는대로 할 수 있도록 되어 있는 것이다. 다양한 형태를 지원하려고 하다 보니 선언문이 복잡해졌다.

실제 프로그래밍에서는 첫 번째 인수로 문자의 타입만 밝히는 정도면 충분하다. 현재 문자를 표현하기 위해 사용할 수 있는 타입은 char, wchar_t 두 가지가 있는데 이 두 가지에 대해서는 다음과 같은 특수화 버전이 미리 선언되어 있다.

 

typedef basic_string<char> string;

typedef basic_string<wchar_t> wstring;

 

string은 ANSI 문자열이며 wstring은 유니코드 문자열을 표현한다. 두 클래스 모두 첫 번째 인수만 지정했으므로 디폴트에 의해 널 종료 문자열이며 new, delete로 메모리를 관리한다. ANSI와 유니코드는 프로젝트의 실행 환경에 따라 선택되어야 하되 두 클래스는 문자 코드만 다를 뿐 멤버 함수의 목록이나 기능이 다른 것은 아니다. 같은 템플릿으로 만들어졌으므로 내부 알고리즘은 동일할 수밖에 없다. 그래서 여기서는 ANSI 문자열을 다루는 string에 대해서만 다루되 wstring도 똑같은 방법으로 사용할 수 있다.

string 클래스는 그 자체로 독립적이기는 하지만 다음 장의 주제인 STL과도 깊은 연관이 있다. 반복자를 사용할 수 있으며 reverse, sort 등의 STL 알고리즘을 string에도 그대로 적용할 수 있고 컨테이너와 함께 사용할 수도 있다. string은 STL의 일부는 아니지만 STL을 만나면 훨씬 더 많은 일을 할 수 있다.