STL과 다른 라이브러리와의 차이점
STL 컴포넌트들에 관한 세부적인 설명에 들어가기에 앞서서, 우선 STL이 기존의 다른 C++ 라이브러리와 어떤 점에서 크게 차이가 나는지 살펴보고 넘어가도록 하자.
우선 첫 번째로, 기존의 C++라이브러리에는 상위 클래스가 동일한 클래스의 객체들만 컨테이너에 담을 수 있다는 제약 사항이 있기 때문에, 이들 라이브러리에서 제공하는 컨테이너들은 사용하기가 불편하다. 하지만 STL 컨테이너에서는 이러한 종류의 상속을 요구하지 않으며, 따라서 내장 타입을 포함한 어떠한 종류의 객체도 담을 수 있다.
두 번째로, 기존의 컨테이너 클래스 라이브러리에서는 알고리즘들이 모두 특정 클래스의 멤버 함수로 구현되어 있다. 예를 들어, 기존의 라이브러리에서 제공하는 리스트 클래스가 find라는 검색 알고리즘을 가지고 있다면, 이 find 알고리즘은 벡터 클래스의 find 알고리즘과는 전혀 관련이 없는 알고리즘이다. 이런 식으로 구성된 라이브러리는 각각의 클래스마다 많은 양의 소스 코드와 문서를 가지고 있어야 하는 매우 큰 문제점을 안게 된다. 컨테이너 종류가 m개이고, 각 컨테이너가 정의해야 할 (find와 같은)연산의 종류가 n개라면, 소스 코드와 문서를 작성해야하는 연산의 개수는 모두 mn개가 된다. 이것을 다 구현하지 않으면, 라이브러리 클래스는 필요한 연산들을 일부 확보하지 못하게 되고, 결국은 특정 클래스를 라이브러리에서 삭제하거나, 서로 다른 두 개의 데이터 구조간에 변환을 수행한 뒤에 원하는 연산을 수행하는 수밖에 없다. 하지만 이러한 변환은 우리가 숙지해야 할 (또는 관리해야 할) 클래스의 개수를 늘어나게 할뿐만 아니라, 런-타임 수행 성능에도 종종 심각한 문제를 초래하게 된다.
STL에서는 이러한 문제점들에 대한 보다 근본적인 해결책을 가지고 있다. m개의 컨테이너와 mn개의 연산을 정의하고 문서화할 필요없이, m개의 컨테이너와 n개의 연산에 대해서만 작업을 해주면 되기 때문에, 결과적으로 기존의 클래스 라이브러리에 비해서 알고리즘과 컨테이너를 선택할 수 있는 유연성의 폭이 훨씬 넓어지게 되고, 익혀야 할 인터페이스의 개수와 관리해야 할 소스 코드의 양도 적어지게 된다.
1. 확장성
모든 것을 다 처리할 수 있는 소프트웨어 라이브러리란 있을 수 없다. 물론, STL도 여기서 예외일 수는 없다. 하지만 라이브러리를 확장하는데 있어서는 STL이 기존의 클래스 라이브러리에 비해 상대적으로 더 유리한 위치에 있다.
기존의 클래스 라이브러리에서는 알고리즘이 특정 클래스에 밀접하게 연관되어 있기 때문에 알고리즘을 새로 추가하려면 라이브러리 클래스를 상속하여 클래스를 새로 만들어야 한다. 이와 같은 방식은 다음과 같은 단점들을 가지고 있다.
- 이러한 방식은 클래스 설계자가 그러한 확장을 미리 예상하고, 이를 충분히 고려하여 클래스를 설계한 경우에만 가능한 것이다. - 종종 라이브러리 클래스의 소스 코드를 수정할 필요가 있는데, 이렇게 클래스의 소스 코드를 직접 수정하게되면 미묘한 버그가 생길 수 있고, 테스팅, 유지 보수, 버전 관리와 관련해서도 문제가 발생할 소지가 다분하다. |
반면에 STL은 개방적이고 독립적인 구조(open and orthogonal structure)로 설계 되었기 때문에 프로그래머가 직접 설계한 알고리즘을 라이브러리가 제공하는 컨테이너와 함께 사용할 수 있으며, 또한 프로그래머가 만든 컨테이너를 라이브러리가 제공하는 알고리즘과 함께 사용할 수 있는 것이다. 이 때, 라이브러리의 소스 코드는 전혀 건드릴 필요가(심지어 읽어볼 필요조차도) 없다.
2. 컴포넌트 상호 교환 가능성(component interchangeability)
STL이 이러한 개방성과 독립성을 갖추게 된 비결은 제조업 분야에서는 익히 잘 알려져 있는, 컴포넌트 상호 교환 가능성 때문이다. 이 상호 교환 가능성은 컴포넌트의 인터페이스를 최대한 단순하고, 일관성 있게 만들어야 획득할 수 있는 것이다.
장비 및 부품 산업에서는 이 상호 교환 가능성이 지닌 장점에 관해서 잘 알고 있다. 소프트웨어 산업에서도 이러한 상호 교환 가능성을 위해 그동안 많은 노력을 기울여 왔다. 수년 전, STL의 선조라 할 수 있는 몇몇 라이브러리에서 시작된, 소프트웨어 컴포넌트 상호 교환 가능성을 향한 부단한 노력은 다른 어떠한 라이브러리 설계 분야 보다도 훨씬 큰 것이었다.
우리는 앞에서 이미 STL의 find 알고리즘과 merge 알고리즘을 통해서 STL 컴포넌트의 상호 교환 가능성에 관한 사항들을 일부 살펴보았다. 이 알고리즘들을 서로 다른 컨테이너에 두루 사용할 수 있는 이유는 시퀀스를 접근할 때 반복자라 불리는, 포인터와 유사한 객체를 통해 제한적으로 접근할 수 있도록 알고리즘 인터페이스를 설계해 두었기 때문이다.
바로 여기서 주목해야할 것은 STL에서는 주어진 알고리즘을 어떤 데이터 구조와 함께 사용해야 되는지에 관한 언급이 없다는 점이다. 대신에, STL에서는 알고리즘과 컨테이너를 다음과 같은 방식으로 서로 연결해 두고 있다.
- 각 컨테이너마다 해당 컨테이너가 반드시 제공해야 하는 반복자 부류를 명시해 둔다. - 각 알고리즘마다 해당 알고리즘이 사용할 수 있는 반복자 부류를 명시해 둔다. |
즉, 데이터 구조를 직접 언급하지 않고 반복자를 이용하여 알고리즘의 의미를 기술함으로써, 데이터 구조의 독립성을 획득할 수 있는 것이다. 바로 이것이 알고리즘을 보다 더 "제네릭"하게 만들 수 있게 하는 요인이며, 결과적으로 각각의 알고리즘이 다양한 데이터 구조와 함께 동작할 수 있는 것이다. 다른 어떠한 라이브러리도 이러한 것들을 STL만큼 잘 해낼 수 있는 라이브러리는 없다.
STL에서는 반복자가 데이터의 시작과 끝을 표시하는 방식에서도 일관성을 유지하고 있다. 각각의 알고리즘은 데이터를 접근 할 때, 이러이러한 방식으로 접근할 것이다라는 일관된 가정을 가지고 있으며, 실제로 컨테이너가 제공하는 데이터 접근 방식도 알고리즘이 가정하고 있는 데이터 접근 방식과 정확히 일치한다.
3. 알고리즘/컨테이너 호환성
STL을 이해하는데 필요한 가장 핵심적인 포인트에 관해 살펴보자. find나 merge와 같이 완전히 제네릭한(즉, 어떠한 종류의 컨테이너와도 동작이 가능한) STL 알고리즘도 일부 있긴 하지만, 모든 알고리즘이 라이브러리의 모든 컨테이너와 함께 사용할 수 있는 것은 아니다.
이론적으로는 모든 STL 알고리즘과 컨테이너 간에는 서로 인터페이스가 가능하지만, 일부러 그렇게 할 수 없도록 해 놓았다. 왜냐하면, 일부 알고리즘은 특정 컨테이너와 같이 사용하면 성능면에서 그다지 효율적이지 못한 경우가 있기 때문이다.
sort 알고리즘의 경우가 아주 대표적인 예인데, sort에서 사용하는 알고리즘(퀵소트를 약간 변형한 것이다)은 데이터에 대한 임의 접근이 가능할 때에만 효율적이다. 임의 접근은 배열, 벡터, 덱에서만 제공하며, 리스트나 다른 STL 컴포넌트에서는 제공되지 않는다. 대신에 list 클래스에서는 sort 멤버 함수를 제공하기 때문에, 이를 이용하면 리스트를 효율적으로 정렬할 수 있다.
"하지만 이렇게 클래스마다 별도의 알고리즘을 가지게 되면, 결국 STL도 기존의 클래스 라이브러리와 별반 다를 것이 없지 않느냐"라고 반문하는 사람이 있을지도 모르겠으나 실제로는 그렇지 않다. 왜냐하면 우선 첫 번째로, find나 merge와 같이 완벽한 제네릭한 알고리즘이 훨씬 더 많다. list의 sort 멤버 함수와 같이 특정 클래스의 멤버 함수로 되어 있는 알고리즘들은 극히 소수이다. 물론, 모든 알고리즘이 다 그러한 것은 아니지만, 제네릭 sort 알고리즘과 같이 다양한 종류의 컨테이너와 동작이 가능한 알고리즘들이 많다.
그리고 두 번째로, 몇 가지 정해진 컨테이너에만 사용할 수 있는 알고리즘들이 있는데, 이들 알고리즘이 어떠한 컨테이너와 사용할 수 있는지를 알아내는 것은 어렵지 않다. 그냥, 알고리즘과 컨테이너가 서로 반복자를 통해서 어떠한 방식으로 결합되는지만 살펴보면 된다.
- STL 튜토리얼·레퍼런스 가이드 제2판 中 -