본문 바로가기

Library/C/C++

컴파일 시점에서의 데이터 컨테이너 구현

컴파일 시점에서의 가변 인자 리스트를 구현할 수 있을까? 물론 가능하다. 여기서 말하는 가변 인자 리스트란, 컴파일 시점에서 정의한 만큼의 크기를 가지는 리스트를 말한다. 즉, 컴파일 시점에서의 컨테이너라고 할 수 있다. 하지만, 이것은 필연적으로 템플릿 코드 안에 static 타입으로 데이터형이 코드 영역에 추가되야 한다는 것을 의미한다. 여기에, static 타입으로 선언된 자료형은 클래스 영역에 걸쳐 한번만 생성된다는 점을 고려해볼 때, 각각의 데이터 타입마다 다른 데이터 값을 넣고 싶을 때 문제가 될 수 있다. 즉, 일반적인 목적에서 본다면 컴파일 시점에서의 가변 리스트 목록이란 그다지 좋은 생각은 아니다. static 컨테이너는 해당 영역에 대해 하나만 존재할 수 있기 때문에, 의도하지 않게 다른 개체에서 사용하는 값을 변겨할 위험성이 있다. 컴파일 시점에서의 컨테이너가 꼭 필요한 것인지 생각해보고, 그렇지 않다면 mpl::vector와 같은 것을 고려해 보는 것이 좋다. 대부분의 경우, mpl은 이미 훌륭한 구현을 제공하고 있다.

그러나, mpl이 대부분의 구현을 가지고 있다고 하더라도 모든 상황에서 만능의 해결책은 아니다. mpl은 필요한 것보다 훨씬 많은 것을 구현하고 있기 때문이다. 예를 들어, 템플릿으로 구현된 단위 전략들이 각기 다른 인자를 요구한다고 했을 때, 이 요구를 만족하기 위해 mpl을 쓰는 것은 닭 잡는데 소 잡는 칼을 쓰는 것과 같이 느껴진다. 이 경우, 보다 간략한 구현이 필요하다. 즉, 어떤 템플릿 호스트 클래스가 첫번째 인자로는 인스턴스 자료형을, 그리고 두번째 인자로는 스레딩 모델을 요구한다고 해보자. 첫번째와 두번째 인자 모두 이미 정해진 템플릿 인자를 가지고 있다면, 이 호스트 클래스는 아무런 문제가 없다. 그러나, 만약 이 호스트 클래스가 주어진 자료형을 각각의 방법으로 활용하는 단위 전략 클래스를 가지고 있고, 각각의 단위전략마다 요구하는 인자가 다르다면 여기서부터는 고정적인 템플릿 인자로는 호스트 클래스 자체를 정의할 수 없는 상황이 생긴다. 물론, 단위전략 자체를 typename T로 그냥 받아버릴 수도 있지만, 그래서는 느슨한 타입체크, 즉 디자인적으로 결함이 발생하게 된다. 바로 여기서, 컴파일 시점에서의 가변 리스트를 생각해볼 수 있다.

만약, 세번째 인자로 컴파일 시점에서의 가변 리스트 목록을 정의한다면, 각각의 단위전략마다 이 리스트에서 데이터형을 검사하고, 필요한 값을 가져올 수 있다. 그리고, 호스트 클래스에서도 단위전략마다 템플릿 특수화를 할 필요없이 한번의 코드 작성으로 문제를 해결할 수 있다. 물론, 단위전략이 가변 리스트에서 잘못된 값을 가져올 위험도 있지만, 그것 역시 컴파일 시점에 데이터형을 파악할 수 있으므로 최소한의 방어전략을 세울 수 있다. 물론, 여기서 디자인적으로 방어를 하기 위해서는 TypeTraits 기능을 할 수 있는 인프라가 준비되어 있어야 한다. 다른 방법으로, 이미 정해진 충분한 크기의 blob와 같은 것도 생각해 볼 수 있지만, blob 형태는 필요하지도 않은 부수적인 다른 데이터까지 강제할 수 있기 때문에 좋은 전략이라고 할 수 없다.

그러나, 컴파일 시점에서의 가변 리스트, 또는 컴파일 시점에서의 컨테이너로 STL 컨테이너를 대체할 수는 없다. 컴파일 시점에서의 데이터 컨테이너란, 어쨌든 컴파일 시점에서 모든 값이 결정되어야 하기 때문에 이런 방법으로 STL 컨테이너를 대신할 수 없다. 위에서도 언급했지만, 비효율적인 템플릿 사용으로 인한 코드 부풀림(code bloating) 현상이 발생할 수 있고, 자료형이 모두 정적 데이터 영역에 포함되는 문제와 엮어진다면 심각한 문제가 될 수도 있기 때문이다. 또, 보다 근본적으로, 모든 값을 컴파일 시점에서 결정할 수 있는 것도 아니다.


사실, 컴파일 시점에서의 데이터 컨테이너란 개념적으로 함수자와 크게 다르지 않다. 함수자는 호출될 때 함수 시그니처에 따라 정확한 함수를 호출할 수 있어야 하므로, 가변 인자에 대한 데이터 타입 정보를 가지고 있어야 하기 때문이다. Loki::Typelist는 이것을 간략하고 미끈하게 표현한 것이라 할 수 있다. 물론, 대부분의 사람들에게는 Loki::Typelist와 같은 것을 직접 만들 이유는 없다. 보다 진보된 functor가 필요하다면 tr1::function이 매우 훌륭하게 구현되어 있다.

직접 구현하고 싶은 사람은 Loki::Typelist를 참고하면 매우 도움이 되며, Loki::Typelist를 조금만 개량하면 이런 기능을 간략하게 구현할 수 있다. 인터페이스는 tr1::tuple이나 Loki의 GenScatterHierchary를 통해 얻어지는 Tuple을 참고하면 좋을 것이다. 누누히 말했듯이 그냥 라이브러리 코드를 쓰는 것이 목적이라고 하더라도, 주말에 취미 삼아 이런 것을 구현해보면 무엇이 성능상의 트레이드 오프 사항인지 더 잘 파악할 수 있게 되며, 결과적으로 더 좋은 코드를 작성할 수 있게 된다.