어제 일종의 지연된 함수 호출 클래스 구현에 관해서 포스팅을 했다. (제한된 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으로 해결할 수 없는 "비슷한 템플릿의 반복"을 해결하는 방법으로는 이게 가장 유용한 방법인 것 같다. 한 번 작성하고 그 이후엔 유지 보수가 상대적으로 필요치 않다면 대안인 "코드 반복 작성", "코드 생성기 작성" 보다 뭔가 더 나은 것을 주는 것 같다. 이 부분은 다음 번에 포스팅하기로 하고(…)
결론적으로,
용자 rein은 boost::preprocessor 사용법을 습득했다!
ps. 조금 생각해보니 boost::preprocessor를 현재 사용 중인 패킷 처리 코드 생성기 부분에도 쓸 수 있을 것 같다.
으악!!! boost_pp를 처음 배울 때 너무 어려워서 즐치고 매크로로 발랐었는데 ‘생각보다는’ 훨씬 readable하잖아! orz
생각보다만 readable(…)
우리가 일상적으로 짜는 코드보다는 훨씬 읽기 힘들긴 하잖아? -_-;;
BOOST_PP는, 모든 언어는 LISP를 향해간다의 한 방향성인거같음 […]
함수형 언어는 syntactic sugar를 추가하고,
절차형/객체지향 언어들은 함수형 언어의 개념을 가져가는게 대세(?)인듯.
Boost.Preprocessor – enum과 문자열의 매핑…
회사에서 발표 준비를 하다가 Boost.Preprocessor(BOOST_PP)를 언급하려고 약간 만져봤는데, 이게 의외로 재밌더군요. 퍼즐 푸는 느낌이기도 하고 새로운 언어를 배우는 느낌이기도 하고, 게다가 당…
[…] 처리해주는 라이브러리이다. 보다 상세한 용법은 공식 홈페이지 혹은 레인님 블로그와 리카넷 블로그를 참조하기 […]