rein's world

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를 현재 사용 중인 패킷 처리 코드 생성기 부분에도 쓸 수 있을 것 같다.