C++ 문자열 나누기

Ruby에도 C#에도 문자열에 대한 split함수가 있다. 그러나 우리의 C++에는 그런 게 없다. 이런 하잖은 기능은 직접 구현해야 한다. 이게 C++이 멋진 이유 아니겠나.

서핑 중, 문자열을 자르는 아주 멋진 코드조각을 stackoverflow에서 발견했다.

vector<string> tokens;

copy(istream_iterator<string>(iss),
    istream_iterator<string>(),
     back_inserter<vector<string> >(tokens));

구차하게 for문을 작성하지 않아도 되는 이런 우아한 방법이 있다니. 쇼크! 내가 얼마나 STL에 무신경했는지 느꼈다.
한눈에 들어오지 않아서 Visual C++ 2005의 STL소스를 참조해서 살펴봤다.

알다시피, copy()함수는 첫 번째 인자로 들어오는 입력 이터레이터를 두 번째 인자로 들어오는 입력 이터레이터까지 순회하면서 그 값을 세 번째 인자인 출력 이터레이터의 위치에 쓴다. back_inserter클래스는 컨테이터에 값을 추가하기 위한 일종의 헬퍼이다.

여기서 내가 가장 헛갈렸던 것은, ‘첫 번째 인자와 두 번째 인자의 값을 비교함에 있어서 istream_iterator는 그 값을 어떻게 가져오며, 비교대상인 istream_iterator클래스의 기본 생성자(두 번째 인자)는 무슨 의미인가’였다.

Visual C++의 STL소스를 뒤져보면, istream_iterator클래스의 기본생성자는 문자열의 char(istream_iterator클래스 기본 템플릿 파라미터)을 가리키는 포인터 변수를 0으로 설정한다. 즉, 종료문자열을 의미한다.

istream_iterator()
: _Myistr(0)
{	
    // construct singular iterator
}

그렇다면, copy()함수는 입력 이터레이터의 값을 어떻게 가져올까? 이는 istream_iterator클래서의 _GetValue()함수를 사용하며, _GetValue()함수는 basic_istream클래서의 >>연산자를 사용한다. 결국, copy()함수는 istringstream클래스에 정의된 >>연산자를 사용하게 된다.

자못 복잡해 보일 수도 있지만, 코드는 아름답다.

여기서 한 걸음 더 나아가서 delimiter를 공백문자가 아닌 다른 걸로 바꾸고 싶었다. istringstream에서는 그러한 능력을 제공하지 않는다. 역시 C++답다. split()함수를 구현하지 않은 마당에, delimiter를 설정하는 것 따위, C++에게는 사치다. 근데, getline()함수에서는 delimiter를 설정할 수 있다. 결국, 다음과 같이 쓸 수 있다.

for (string token; getline(iss, token, 'i'); tokens.push_back(token));

어쨌든, 한 줄은 한 줄. – -;

교훈 : STL소스는 복잡하다.