메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

표준 템플릿 라이브러리(STL, Standard Template Library)를 왜 사용해야 하는가?

한빛미디어

|

2004-08-26

|

by HANBIT

19,426

저자: 김승태 / 유진로보틱스 주임연구원

C++ 언어는 클래스, 멤버함수, 상속, 템플릿, 오버로딩 등의 언어적인 기술 외에 이들을 기반으로 필요하리라 예상되는 기능을 미리 만들어 둔 클래스, 함수 등의 집합체인 라이브러리를 제공한다. 그리고 이를 표준 라이브러리(Standard Library)라고 부른다. 이 표준 라이브러리에는 C 언어에서 널리 사용되던 도구가 들어있어 이를 이용해 C++에서도 입출력, 문자열 처리, 메모리 관리 등을 손쉽게 할 수 있다.

1980년 뱐 스트라우스트럽(Bjarne Stroustup)에 의해 탄생한 C++ 언어는 몇 번의 역사적인 큰 사건을 겪고 오늘날의 모습을 갖추게 되었다. 그 중 하나가 바로 1994년에 있었던 표준 템플릿 라이브러리의 도입이다. 이 변화는 알렉산더 스테파노프(Alexander Stepanov)가 주도하였다. 알렉스는 표준 라이브러리에 템플릿(Template)을 기반으로 한 클래스와 함수를 추가로 도입할 것을 제안하였고, 이 클래스와 함수를 “컨테이너(Container)”와 “알고리즘(Algorithm)”라 불렀다. 추가 요청을 한 클래스가 스택(Stack), 큐(Queue) 등의 데이터 구조이고, 함수는 대다수가 이 데이터 구조와 함께 연동해 작업을 수행하기 때문에 이 이름을 선택한 것이다.

템플릿 기반의 클래스와 함수는 미국 표준 위원회(ANSI, America National Standard Institute)에서 제정하는 C++ 표준에서 1995년에 처음으로 표준 템플릿 라이브러리(STL, Standard Template Library)라는 이름으로 만장일치로 도입되었고, 이후 국제 표준화 기구가 제정하는 C++ 언어에서 표준 라이브러리와 완전히 흡수되면서 표준 라이브러리로 통합되었다. 그리고 표준은 기술 보고서 TR1(Technical Report)에 있는 내용을 중심으로 표준 라이브러리를 계속 확장하고 있다.

표준 템플릿 라이브러리가 표준 라이브러리로 통합되어 이제 표준 템플릿 라이브러리라는 명칭을 사용하는 것은 어쩌면 역사적인 사건을 가리킬 때나 사용해야 올바른 것일지도 모른다. 그러나 현재도 표준 템플릿 라이브러리를 처음 구현했던 HP의 STL을 계승한 SGI에서도 여전히 표준 템플릿 라이브러리라는 용어를 사용하고 있으며, 많은 국제 C++ 언어 전문가들 역시 표준 템플릿 라이브러리라는 용어를 사용하고 있다. 비록 C++ 언어의 국제 표준(IS, International Standard)인 14882에서는 공식적인 언급이 전혀 없지만.

표준 템플릿 라이브러리는 컨테이너(Container), 알고리즘(Algorithm), 이터레이터(Iterator), 그리고 함수 개체(Functor, Function Object)로 크게 구성되어 있다. 이들에 대해 각각 살펴보자.

• 컨테이너
같은 타입의 원소를 관리하는 선형적인 데이터 구조를 지원하는 클래스로, 일종의 지능화된 동적 배열이다. 원소를 추가하거나 삭제할 수 있고, 크기 변경에 따라 내부에서 메모리를 관리해서 개발자가 메모리를 직접 관리하는 부담을 줄여준다. 컨테이너에는 크게 시퀀스 컨테이너 계열(Sequence Container)과 연관 컨테이너 계열(Associated Container)이 있는데, 시퀀스 컨테이너 계열은 원소를 스택, 큐, 리스트 등의 데이터 구조에 담아 관리하고, 연관 컨테이너 계열은 균등 트리(Balanced Tree)에 담아 원소를 관리한다. 이 외에도 현재 표준화가 진행 중인 C++03(ISO/IEC 14882:2003의 약칭)의 차기 버전인 C++0x에서는 해시 기반의 연관컨테이너(Hashed Associated Containers)가 새롭게 추가될 것으로 기대된다.

• 알고리즘
이터레이터를 통해 시퀀스 컨테이너에 일정한 구간에 정하고, 이에 대해 복사, 전환, 병합, 정렬 등의 연산을 지원한다. 이들은 크게 입력된 이터레이터가 가리키는 곳에 변화를 주는 변형 알고리즘과 변화를 주지 않는 불변형 알고리즘, 정렬에 관련된 정렬 알고리즘으로 분류된다. 변형 알고리즘에는 구간 내 원소의 순서를 뒤 섞는 random_shuffle, 조건에 맞는 원소를 교체하는 replace, 구간 내 원소를 모두 같은 값으로 바꾸는 fill, 구간 내 원소를 주어진 함수가 반환하는 값으로 바꾸는 generate, 조건에 맞는 원소를 제거하는 remove, 동일한 값을 가지는 원소를 제거하는 unique 등이 있다. 불변형 알고리즘에는 주어진 구간에 있는 모든 원소에 접근하여 for 문의 역할을 할 수 있도록하는 for_each, 두 시퀀스를 비교하여 다른 곳이 있는지를 찾는 mismatch, 두 시퀀스가 같은 값을 가진 원소들인지를 확인하는 equal 등이 있다. 정렬 알고리즘에는 구간 내 원소를 정렬하는 sort와 안정적인 조건을 추가로 하는 stable_sort가 대표적이고, 상위 n개만 정렬하는 nth_element, 주어진 값을 갖는 최초 원소를 찾는 lower_bound, 마지막 원소 바로 다음을 찾는 upper_bound, 주어진 값을 갖는 구간을 가리키는 이터레이터를 반환하는 equal_range, 두 시퀀스를 합치는 merge, 집합 연산을 제공하는 set_union, set_intersection, set_diiference 등이 있다.

• 이터레이터
원소를 관리하는 방법을 제공하는 컨테이너의 위상에 걸맞게 포인터의 개념을 한층 일반화시킨 지능화된 포인터다. 이터레이터는 내부적으로 실제 포인터를 관리하는 클래스며, 포인터를 이용해 다양한 형태의 데이터 구조를 일관된 방법으로 원소에 접근할 수 있는 방법을 제공한다. 이터레이터에서는 포인터를 사용하는 데 쓰는 연산자인 *, ->, ++, -- 등을 이터레이터에 맞게 오버로딩하였기 때문에 일반의 포인터와 같은 방식으로 사용할 수 있다. 즉, 사용 목적에 따라 입력용 InputIterator, 출력용 OutputIterator, 입출력용 ForwardIterator, 양방향 BidirectionalIterator, 임의 접근용 RandomAccessIterator 등으로 기능을 세분화하여 그 이름으로부터 손쉽게 이터레이터의 역할을 이해할 수 있도록 하고, 기능을 제한함으로써 잠재적으로 날 포인터(Raw Pointer)를 무분별하게 사용하기 때문에 발생할 수 있는 에러의 가능성을 원척적으로 봉쇄하는 역할을 한다.

• 함수 개체
함수개체는 연산자 ( )를 오버로딩한 클래스로, 컨테이너, 알고리즘 등에서 비교를 위해 연산자 <를 대체하는 등의 지능화된 함수 역할을 한다. 표준 라이브러리에서는 많이 쓰는 기능을 중심으로 함수 개체를 미리 정의하여 제공하고 있다. 이들은 크게 산술 계산용으로 사용되는 plus, minus, multiplies, divides, modulus, negate, 비교 연산에 사용되는 equal_to, not_equal_to, greater, less, greater_equal, less_equal, 논리 연산에 사용되는 logical_end, logical_or, logical_not으로 구분된다. 그러나 함수 개체 자체가 함수형 프로그래밍의 기본적인 도구가 되지만, 표준 템플릿 라이브러리가 완전한 함수형 패러다임을 하는 것은 아니다.

• C++ STL 실전 프로그래밍

표준 템플릿 라이브러리에는 기존의 라이브러리와 달리 몇 가지 큰 장점이 있다. 스택, 리스트, 큐, 균등 트리 등의 데이터 구조에 대한 직관적인 인터페이스를 제공해 사용자가 손쉽게 사용할 수 있다. 또한 정렬, 이진 탐색, 찾기, 병합(Merge), 최대 원소 관리, 상위 n개의 원소 검색 등의 고급 알고리즘도 제공한다. 이런 고급 연산 기능은 기존 라이브러리에서 제공하는 저수준 기능과는 크게 달라진 점이다. 그리고 이 기능은 예외 처리를 기반으로 제공된다. 예를 들어 컨테이너에 저장되어 있는 어떤 원소에 대해 접근하기 위해 사용되는 함수 at은 지정된 매개변수가 접근할 수 있는 범위를 넘어설 경우 예외 out_out_range를 던진다. 이와 같이 많은 에러가 발생하는 상황에 대해 예외를 정의해 놓아 프로그램이 언제나 제어가 가능한 안전한 상태에 놓일 수 있도록 해 주고 있다. 포인터를 이용한 배열형 메모리의 관리에서 매번 직접 인자를 검사해야 하는 것과는 큰 차이가 있다.

표준 템플릿 라이브러리의 가장 큰 매력은 더 이상 포인터를 관리하지 않아도 된다는 데 있다. 고급 컨테이너와 알고리즘, 그리고 이터레이터가 제공하는 프로그래밍 구조는 포인터가 필요한 대부분의 경우를 대체할 수 있도록 해 주고, 더불어 높은 수준의 에러 처리 구조와 지능적인 연산에 의해 포인터를 직접 사용할 때보다 훨씬 더 안전하고, 효율적으로 데이터를 관리할 수 있도록 도와준다.

두 번째로 표준 템플릿 라이브러리는 제네릭 프로그래밍(Generic Programming)을 기반으로 이루어져 있다. 즉, 템플릿을 기반으로 제공되는 클래스와 함수들은 이들 내부에서 사용하고 있는 각 타입들을 많은 경우 템플릿 매개변수화하여 제공하고 있고, 이에 따라 실행 시간에 비용을 지불하지 않는 높은 효율성을 지닌 프로그램의 일반화가 가능하도록 해 준다.

또한, 표준 템플릿 라이브러리는 표준의 일부이기 때문에 g++, 비주얼 C++ 등의 특정한 번역 환경을 가정하지 않아 이를 기반으로 작성된 프로그램이 어느 곳에서나 동일하게 번역되고, 실행되는 것이 보장된다. 즉 작성된 프로그램의 이동성(Portability)이 최대한 보장되는 것이다.

이러한 매력에도 불구하고 많은 C++ 언어 사용자들이 표준 템플릿 라이브러리의 사용을 꺼려하고 있다. 표준 템플릿 라이브러리라고 하면 어려운 전문가의 도구, 혹은 뭔가(?) 특별한 목적으로 사용하는 것으로 생각하는 경우가 많기 때문이다. 하지만 지금까지 말해왔듯 그렇지 않다. 스택, 리스트, 트리 등의 데이터 관리와 정렬, 합병, 제일 큰 원소 찾기 등의 기능은 아주 일반적인 것이다. 많은 프로그램에서 이를 직접 구현해 사용하고 있을 것이다. 표준 템플릿 라이브러리는 바로 이런 일을 대신하는 것을 목적으로 만들어졌다. 대단한 무언가가 절대 아니다. 사용자는 단지 자신이 사용할 컨테이너와 알고리즘을 필요에 따라 선택하고, 사용하기만 하면 되는 것이다.

템플릿(Template)이라는 이름 때문에 쉽게 범접할 수 없는 어려움이 느껴지지만 템플릿의 기본 기술을 사용할 수 있는 정도만 파악한다면 표준 템플릿 라이브러리를 사용하는 것은 그다지 어렵지 않다. 오히려 사실 이제 막 C++ 언어를 배우려는 개발자는 표준 라이브러리를 사용하는 것부터 시작하는 편이 좋다. 표준 라이브러리는 직관성이 높은 라이브러리인 동시에 높은 수준의 연산을 지원해 주기 때문이다. 그래서 C++ 언어 문법의 가장 기초적인 것만 파악하고 있어도 표준 라이브러리를 사용하여 프로그램을 작성하는 데는 전혀 문제가 없다. 오히려 반드시 해야 할 에러 처리 등을 생각하면 이를 직접 수행하는 것보다 표준 라이브러리의 에러처리에 의존하는 것이 훨씬 더 이득이 된다. 그렇기 때문에 많은 전문가들이 표준 라이브러리를 사용하는 것에서부터 C++ 언어를 시작하기를 추천한다.

이 외에도 작업 환경에 대한 지원 때문에 표준 템플릿 라이브러리의 사용을 꺼려하기도 한다. 가장 일반적인 환경이 비주얼 C++, g++ 등일 텐데, 표준 템플릿 라이브러리 자체가 길어야 10여년 전에 탄생되었던 점을 감안하면 이들 환경에서 표준 템플릿 라이브러리를 제대로 지원하기 시작한 것은 그다지 오래된 일이 아니다. 그래서 애써 표준 템플릿 라이브러리를 배워 프로그램에 적용하더라도 번역이 안되거나 거의 암호화된 수준의 에러 메시지를 만나 당황했을 것이다. 하지만 비주얼 C++ 6.0 버전 이상, .NET, g++3.3 버전 등의 비교적 최신 컴파일러에서는 이제 표준 템플릿 라이브러리를 거의 완벽하게 지원하고 있다. 아직 세부 구현에 실수가 있긴 하지만 대다수의 실제적인 적용에 있어 심각한 문제를 초래하는 경우는 거의 없다. 또한 STLFilt 등의 널리 알려진 무료 진단 도구는 복잡한 에러 메시지를 간단한 형태로 바꾸어 에러 메시지의 내용을 쉽게 이해할 수 있는 형태로 바꿔주므로 이런 도구를 적극적으로 이용하는 것도 좋다.

뜻밖에도 개발자 중에는 왜 표준 템플릿 라이브러리를 사용하지 않는가에 대한 대답으로 “MFC를 사용하는데 또 STL을 사용해야 할 이유가 있는가?”라고 반문하는 경우가 많았다. 그런데 이는 표준 템플릿 라이브러리에 대한 잘못된 이해로 생기는 의문이다. 표준 템플릿 라이브러리는 MFC(Microsoft Class Library)와 아무 관계가 없다. 아니, MFC와 표준 템플릿 라이브러리를 함께 사용해야 더 강력한 프로그램을 작성할 수 있다. MFC와 표준 템플릿 라이브러리가 추구하는 목적이 근본부터 다르다. 표준 템플릿 라이브러리는 컨테이너, 알고리즘 등 데이터 구조와 관련된 인터페이스를 플랫폼 독립적인 방법으로 제공하는 것이 주목적이지만, MFC는 GUI(Graphics User Interface)를 윈도우라는 실행 환경에서 쉽게 프로그램화할 수 있도록 하는 것이 주목적이어서 서로 겹치는 부분이 거의 없다. 더구나 이들을 구성하는 패러다임 역시 전자는 명령형, 제네릭, 함수형 패러다임을 섞어 쓴 반면, 후자는 개체지향적이다. 물론 비슷한 목적의 요소가 다소 있기는 하다. 문자열을 관리하는 CString과 표준 라이브러리의 string, 리스트를 구현한 CObList와 표준 라이브러리의 list 등이 그것이다. 하지만 적어도 필자의 경험으로는 표준 라이브러리가 MFC 라이브러리보다 훨씬 나은 방법을 제공하고 있으며, 아마 표준 라이브러리를 사용한다면 여러분도 나와 같은 경험을 하게 될 것이다.

표준 템플릿 라이브러리를 아직 사용하지 않는 C++ 프로그래머라면 지금부터 접해보라고 자신있게 말할 수 있다. 먼저 시퀀스 컨테이너의 일종인 vector를 malloc으로 관리하는 동적 배열을 대체하는 것으로부터 시작해 보라. 전혀 새로운 C++ 세계를 경험할 것이다.
TAG :
댓글 입력
자료실

최근 본 상품0