Boost::preprocessor 로 템플릿 코드 생성
어제 일종의 지연된 함수 호출 클래스 구현에 관해서 포스팅을 했다. (제한된 Closure라기보단 이 쪽 의미에 가까운 것 같다) 거기에다가 “뭔가 인자 수에 따라 다 정의해야 하는 것을 피할 방법이 없는가?” 라고 썼었는데, Boost 라이브러리에 있는 매크로 메타프로그래밍 라이브러리인 Boost::PP를 소개 받았다.
일단 boost::preprocessor
를 사용해서 구현한 코드는 다음과 같다.
#define ClosureMemberDecl( z, n, unused ) A##n m_A##n;
#define ClosureMemberEnum( z, n, unused ) A##n a##n
#define ClosureMemberInit( z, n, unused ) m_A##n( a##n )
// Create Closure0, Closure1, ...
#define ClosureClassDecl( z, n )
template< typename T, typename R BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS( n, typename A ) >
class Closure##n : public Closure {
protected:
typedef boost::function<R (T& BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS( n, A ) )> FT;
T& m_Obj;
FT m_Function;
BOOST_PP_REPEAT( n, ClosureMemberDecl, ~ )
public:
Closure##n( T& obj, FT func BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM( n, ClosureMemberEnum, ~ ) )
: m_Obj(obj), m_Function(func) BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM( n, ClosureMemberInit, ~ ) { }
virtual ~Closure##n() { }
virtual void Execute() {
m_Function( m_Obj BOOST_PP_COMMA_IF( n )
BOOST_PP_ENUM_PARAMS( n, m_A ) );
}
};
// Create ClosureHelperDecl for corresponding Closure class
#define ClosureHelperDecl( z, n )
template< typename T, typename R BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS( n, typename A ) >
boost::shared_ptr<Closure> MakeClosure( T& obj,
R (T::*func)( BOOST_PP_ENUM( n, ClosureMemberEnum, ~ ) )
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM( n, ClosureMemberEnum, ~ )
)
{
return boost::shared_ptr<Closure>( new Closure##n < T, R BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS( n, A )>
( obj, func BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS( n, a ) ) );
}
#define ClosureDecl( z, n, unused )
ClosureClassDecl( z, n )
ClosureHelperDecl( z, n )
// 최대 인자 수를 다음 매크로의 첫인자로
BOOST_PP_REPEAT( 5, ClosureDecl, ~ )
BOOST_PP_
로 시작되는 매크로들이 boost/preprocessor.hpp
에 정의되어 있는데, 이 매크로들을 사용해서 template 정의를 “반복” 시킨다. 코드가 반복되지 않아도 되는 것은 좋은데, 유지보수와는 한 천광년쯤 먼 곳에 와버린 기분.
다만 실제로 생성되는 코드 – 그러니까 C/C++ 전처리기를 통과시킨 후의 결과 – 는 별도의 방법으로 확인할 수 있긴 하기 때문에 작업 진행은 코드 생성기를 작성하는 것보다_약간 편하다_.
boost preprocessor 라이브러리에 대한 인상을 정리해보면 boost::mpl 처럼 C++ 코드를 C++ 도메인 안에서 생성 할 수 있게 해준다는 “엄청난 장점” 이 있다. 일일이 코드 제네레이터를 “만들거나”, “실행시키는 것"은 귀찮은 일이다.
다만 boost mpl도 그렇지만 이 라이브러리 역시 유지/보수에는 부적합할 수 있다. 손으로 튜닝해야 할 일이 어느 정도 있는 부분에선 사실 상 쓸 수 없을 것 같기도 하다. C++ template meta programming 을 처음 접했을 때처럼 “벽"이 있는 상태고, mpl과는 달리 코드를 읽는 것도 어렵다. C++ 내의 언어이긴 하지만 매크로 자체가 C++ 문법과는 좀 이질적이기 때문에 쉽사리 손댈 수 없는 코드를 작성하게 될 것 같다 -_-;;
다만 template과 template meta programming으로 해결할 수 없는 “비슷한 템플릿의 반복"을 해결하는 방법으로는 이게 가장 유용한 방법인 것 같다. 한 번 작성하고 그 이후엔 유지 보수가 상대적으로 필요치 않다면 대안인 “코드 반복 작성”, “코드 생성기 작성” 보다 뭔가 더 나은 것을 주는 것 같다. 이 부분은 다음 번에 포스팅하기로 하고(…)
ps. 조금 생각해보니 boost::preprocessor를 현재 사용 중인 패킷 처리 코드 생성기 부분에도 쓸 수 있을 것 같다.