dev.log2004. 2. 3. 14:03
루프는 매우 일반적인 프로그램 제어 구성요소로 어떤 언어든지 루프구조를 지원하고 있다. 심지어는 어셈블리에서조차 LOOP 키워드를 통해서 표현이 가능하다. 루프는 프로그래밍 언어를 처음 배울때 부터 익혀온 워낙 친숙한 개념이라 대부분의 프로그래머는 반복작업을 해야할 때 별 생각없이 루프를 작성하곤 한다. 하지만 조금만 생각을 해보면 어떨까.

가장 흔한 예로, 어떤 배열의 원소를 모두 표준 스트림으로 출력하는 작업을 생각해 보자.
대부분은 다음과 같은 코드를 작성할 것이다.
int a[SIZE] = { 1, 2, 3, 4, 5 }:
for ( int i = 0; i < SIZE; i++ )
print_int( a[i] );


출 력 형태가 아름답지 못한 점은 당분간 신경 쓰지 말고, 코드의 구성방식에 대해서만 생각해 보자. C++에서는 저런 끊임없이 나타나는 단순반복 작업을 위한 알고리즘 템플릿이 마련되어 있다. C++사용자라면 대부분 알고 있겠지만, for_each 알고리듬이 그것이다. for_each를 사용하면 다음과 같이 바뀐다.

int a[SIZE] = { 1, 2, 3, 4, 5 };
for_each( a, a+SIZE, fun_ptr( print_int ) );


for_each 는 말 그대로, 시작 이터레이터로부터 마지막 이터레이터까지 모든 구성요소에 대해 주어진 작업을 수행하는 알고리듬을 구현한 함수이다. 세번째 인자에는 fun_ptr 헬퍼 펑션을 사용하여 print_int 함수를 함수어댑터 객체로 포장하여 사용하였다.
그 런데, 분명히 for_each는 '이터레이터'를 받아들인다고 하였는데, 여기서는 배열이름, 즉 포인터를 사용하였다. 이상하다고 생각하는 독자가 있다면, for_each 알고리듬은 템플릿이라는 점을 다시한번 상기하기 바란다. for_each가 요구하는 이터레이터의 조건은, ++멤버연산자가 정의되어 있고, *(역참조)연산자가 정의되어 있으며, 세번째 인자로 주어지는 함수객체의 첫번째 인자의 타입과 *(역참조)연산자가 리턴하는 타입이 같으면 그것으로 만족이다. 이미 알고 있는 바와 같이, 포인터타입에는 ++연산자와 *연산자가 built-in 연산으로 정의되어 있으며, print_int 함수의 인자 타입 int와 *연산자의 리턴타입이 일치하므로 아무 문제 없이 for_each의 인자로 쓰일 수 있다.

물론 for_each는 C스타일의 배열보다는 STL컨테이너와 함께 쓰일때 더욱 유용하다. 만약 지금까지 아무 생각없이 C++로 for, 혹은 wile루프를 작성하고 있었다면 for_each알고리듬으로 바꿔보는 것은 어떨까. 물론 for_each를 쓰는 것이 어색하고 직관적이지 않을 때도 있다. 그러나 STL 알고리듬을 쓰는 연습을 하면 객체지향적인 사고를 기르는데 도움이 된다. 이미 밝혔듯이, for_each를 비롯한 많은 알고리듬들이 수행할 작업을 지정하기 위해 함수객체를 사용한다. 함수객체는 ()연산자, 즉 function call operator를 오버로딩한 클래스의 객체로서, 함수호출을 캡슐화한 객체를 말한다. 즉, STL알고리듬을 사용하는 연습을 통해 작은것부터 객체로 생각하는 습관을 기르는데 도움이 된다고나 할까, 그런 잇점이 있을 수 있다.

물론, 그보다 더 큰 잇점은 깔끔하고 안정적인 코드를 얻을 수 있다는 것이다. 우리가 아무리 루프를 주의깊게 작성해도 범위를 벗어난다던가, 아니면 무한루프로 빠지는 버그를 만들지 않는다고 장담할 수는 없다. 그러나 STL알고리듬은 이미 검증된 코드이므로 우리가 직접 만드는 루프보다 그러한 문제는 확실히 적어질 것을 기대할 수 있다.
Posted by uhm