The Modernest C++ #4 - 컴포넌트와 템플릿 메타프로그래밍 上
[이전 글]
The Modernest C++ #1 - 템플릿 메타프로그래밍 (Template Metaprogramming)
The Modernest C++ #2 - 타입을 가지고 놀아보기, type_traits
The Modernest C++ #3 - 수학 라이브러리
컴포넌트와 템플릿 메타프로그래밍 - 上
본 글은 끼로님의 컴포넌트 기반 개발 디자인 기법을 보고 영감을 얻어 템플릿 메타프로그래밍을 각색한 방법을 설명하는 글입니다.
우리는 이전 시간까지 템플릿 메타프로그래밍의 전반적인 내용을 배웠습니다. 템플릿을 이용하여 수학적 연산도 했으며, 타입을 검사하고 조작하는 기능을 다뤄보면서 이점이 무엇인지를 알게 되었고 반대로 템플릿 메타프로그래밍만의 가독성과 같은 약점도 알게 되었습니다. 하지만 우리는 이 강력한 기능을 좀 더 유용하게 이용할수 있어야 합니다. 글을 쓰는 저나 독자 분들이나 게임을 개발하시는 분들이기에 게임을 개발하는 입장에서 템플릿 메타프로그래밍을 어떻게 써야 할까 많은 고민을 했었습니다.
그래서 떠오른 부분이 바로 오브젝트 설계 방법에 있었습니다, 우리는 게임 내부에서 어떤 행동을 하거나 보여지거나 하는 모든 오브젝트들을 어떻게 구현해야할지에 대한 최선책이 필요했고 수 많은 방법이 있겠지만 저로써는 컴포넌트 기반 개발에 초점을 맞춰 지금까지 개발을 해왔었습니다. 컴포넌트 기반 개발은 채택하기 충분한 이점도 있지만 그에 맞는 약점도 분명히 존재합니다. 우리는 그 약점들을 템플릿 메타프로그래밍으로 보완해볼수는 없을까? 하고 의문을 던져보아야 합니다.
거의 모든 컴포넌트 기반 개발의 객체 구조는 이렇습니다. 게임 오브젝트 객체는 컴포넌트들을 map 컨테이너와 같은 메모리 공간에 저장하고 필요할때마다 꺼내 쓰게 됩니다. CBaseComponent는 모든 컴포넌트의 최상위 부모 객체이며 이와 같은 컴포넌트를 ‘최상위 컴포넌트’ 라고 부르도록 하겠습니다. C++ 언어의 특성상, 모든 컴포넌트 타입에 맞는 컨테이너는 구현이 불가능하기 때문에 우리는 이것들을 최상위 컴포넌트의 포인터 형식으로 저장해야 합니다. (e.g. CBaseComponent*)
모든게 완벽해보입니다, 컴포넌트는 필요에 따라 꺼내 쓰면되고 집어 넣기만 하면 충분하지만 문제가 발생합니다. 우리는 어떻게 무수하게 많을수도 있는 이 컴포넌트들을 어떻게 구분하는가가 문제입니다. 우리는 컴포넌트를 게임 오브젝트에서 가져오기 위하여 컴포넌트를 구분할때 쓰는 값을 앞으로 ‘식별자 (identifier)’ 라고 부르도록 하겠습니다. 식별자는 문자열이 될수도 있으며 그 어떤 모든 상수값이 될수 있습니다. 그저 같은 값이 아니라 컴포넌트 마다 충돌되지 않도록 다르게 값을 부여해주면 됩니다.
많은 분들이 이런식으로 게임 오브젝트에서 컴포넌트를 집어넣고, 가져오시기도 합니다. 하지만 이 방법들에는 분명한 문제점이 있습니다. 바로 오타가 발생할수도 있다는 점, 실행 속도가 부담이 된다는 점과 더불어 생산성(productivity)에서 크게 약한 모습을 보여줍니다.
문자열의 경우에는 스펠링이 틀린 경우 컴파일러에서 에러로 처리해주지 않기 때문에 프로그래머가 쉽게 알아차리지 못할수도 있습니다. 또한 문자열은 값 자체를 컨테이너 내부에 저장하기 때문에 그를 비교할때나 해싱하는 과정에서 부하가 발생할수 있으며 특히 열거자를 이용하면 새로운 컴포넌트 객체를 설계하거나 잘못 설계하여 컴포넌트 자체를 제거할때 매번 열거자에 값을 추가/삭제 해줘야 한다는 점 때문에 생산성에 큰 영향을 미치기까지 합니다.
우리는 어떤 방법을 이용해야 오타 방지, 속도 개선, 생산성 면에서 이득을 볼수 있을까요? 우리가 지금까지 배웠던 내용인 템플릿을 이용하면 세마리 토끼를 한번에 잡을수 있습니다.
어떤가요? 컴포넌트를 얻어올때 이용한 불필요한 매개변수를 더이상 쓸 필요가 없어졌으며 사소한 것일지도 모르지만 리턴 값을 자동으로 맞춰주게 됩니다. 기존의 문자열이나 열거자를 이용하는 방법에서는 버그 발생 가능성등의 이유로 최상위 컴포넌트를 리턴해야 했지만 위 코드에서는 CFooComponent를 반환하게 됩니다. 무엇보다 이 방법은 새로운 컴포넌트를 설계할때마다 새로운 값을 추가해 줄 필요가 없게 되었으며, 문자열 처럼 스펠링을 틀려도 컴파일러가 잡아주기 때문에 굉장히 우아한 방법이라고 할수 있습니다.
하지만 타입 그 자체를 식별자로 이용할까요? 타입을 정수로 바꿀수는 없고 마땅한 방법이 생각나질 않습니다. 우리는 이럴때마다 꼼수(?)를 이용해줘야 합니다. 바로 정적(static) 멤버를 이용해주면 됩니다. 정적 멤버는 인스턴스당 하나씩 존재하는 데이터가 아니라 타입당 하나씩 존재하는 유니크한 데이터이기 때문에 이를 이용해주면 됩니다. 어떻게 이용할지 감이 잘 안잡히실수도 있습니다. 아래의 코드를 살펴봅시다 :
자 코드만 보셔도 감이 오시리라 믿습니다, 컴포넌트 마다 정적 멤버로 컴포넌트 식별자 객체를 가지고 있도록 하였습니다. 그리고 가장 중요한 부분인 컴포넌트의 식별자를 컴포넌트 식별자 객체의 주소 값으로 대체하였습니다. 컴포넌트를 집어넣는 게임 오브젝트의 입장에서는 어차피 템플릿이니 그저 정적 함수를 호출해주는 것만으로도 고유 식별자를 얻어올수 있게 되었습니다.
게임 오브젝트 객체 내부에서 이런식으로 컴포넌트의 식별자를 얻어와서 컴포넌트를 집어넣고 가져오게 됩니다. 이 코드에서는 매개변수로 문자열을 받지 않으므로 속도 상에서 이득을 볼수 있었습니다. 만일 컴포넌트가 아닌 엉뚱한 객체를 템플릿으로 넘겨준다고 해도 컴포넌트 식별자 객체가 없을테니 오류가 발생할수 밖에 없을겁니다.
자, 우리는 이 글에서 컴포넌트 기반 개발에 템플릿을 이용하면 얼마나 매끄럽고 우아한 코드가 나오는지 볼수 있었습니다. 다음 글에서는 컴포넌트 식별자 객체를 조금 더 템플릿 메타프로그래밍을 이용하여 재미있는 기능들을 만들어보려 합니다. 기대해주세요!