generic_visitor: Visitor의 일반적인 함수자를 이용한 구현.

이원구님의 C++ of the Day #33 – dynamic visitor를 읽다가 문득 생각이 났다. Visitor를 generic하게 구현할 수 있지 않을까???

그래서 했다.

다운로드: generic_visitor.cpp

핵심 클래스는 visit_unit이다.

01template<class Visitable>
02struct visit_unit
03{
04public:
05    typedef typename boost::function<void (Visitable&)> visit_function;
06 
07    void register_visitor(visit_function func){...}
08    void visit(Visitable& visitable){...}
09};
10 
11struct default_generic_visitor
12{
13public:
14    void register_visitor(){}
15    void visit(){}
16};

대략 위와 같은 구조를 하고 있는데, visit_unit은 Visitable클래스에 대한 함수자를 vector로 관리해주는 녀석이고, default_generic_visitor는 연쇄상속기법을 사용하기 위해 만든 마지막 Tail클래스다.

연쇄상속기법을 이용하는 코드는 다음과 같다. (ㄷㄷㄷ)

01template<class visitable_list>
02struct generic_visitor_
03    : public boost::mpl::if_c<
04    boost::mpl::size<visitable_list>::type::value != 1,
05    generic_visitor_<typename boost::mpl::pop_front<visitable_list>::type>,
06    default_generic_visitor
07    >::type
08    , public visit_unit<typename boost::mpl::front<visitable_list>::type>
09{
10    using visit_unit<typename boost::mpl::front<visitable_list>::type>::visit;
11 
12    typedef typename boost::mpl::if_c<
13        boost::mpl::size<visitable_list>::type::value != 1,
14        generic_visitor_<typename boost::mpl::pop_front<visitable_list>::type>,
15        default_generic_visitor
16    >::type parent;
17 
18    // 다중상속 관계때문에 함수를 빌려와야한다.
19    using parent::visit;
20};

boost::mpl을 많이 빌려왔다.. 🙂 generic_visitor_는 일종의 구현부인데, 자신이 보고있는 visitable_list의 크기가 1이면 default_generic_visitor를 상속받고, 아니면 visitable_list의 제일 앞놈을 뺀 generic_visitor_를 상속받는다. 또한, visit()등의 함수를 실제로 구현하는 visit_unit을 별도로 상속받는다.

그리고, generic_visit_에 의해 생성된 상속트리를 generic_visitor가 상속받는다.

01template<class visitable_list>
02struct generic_visitor
03    : generic_visitor_<visitable_list>
04{
05    template<class T, class F>
06        void register_visitor(F f)
07    {
08        boost::function<void (T& t)> func(f);
09        static_cast<visit_unit<t>*>(this)->register_visitor(f);
10    }
11 
12};

이녀석은 register_visitor의 템플릿 버젼을 구현하고 있다. 요렇게 static_cast를 해주면 해당하는 녀석을 매우 잘 찾아간다.

이렇게 이야기하면 이해가 안갈거다. 그래서 그림으로 그려보기로 했다.

먼저, visitable_list를 (AClass, BClass, CClass, DClass)로 표기한다. 화살표를 쏘는 쪽이 부모다.

default_generic_visitor

generic_visitor_<visitable_list = (DClass)> <- visit_unit

generic_visitor_<visitable_list = (CClass, DClass)> <- visit_unit

generic_visitor_<visitable_list = (BClass, CClass, DClass)> <- visit_unit

generic_visitor_<visitable_list = (AClass, BClass, CClass, DClass)> <- visit_unit

generic_visitor<visitable_list = (AClass, BClass, CClass, DClass)>

대략 이런식의 상속관계가 된다.

해당 클래스에 대한 함수등록 메소드인 register_visitor나 실제 함수 호출을 해주는 visit 메소드는 visit_unit 클래스 템플릿에 구현이 되어있으며, 나머지는 저 상속관계를 자동으로 만들어주기 위한 코드일 뿐이다.

실제로 쓰려면 다음과 같다.

01void visit_a(AClass& a)
02{
03    std::cout << "visit_a()" << std::endl;
04}
05void visit_b(BClass& b)
06{
07    std::cout << "visit_b()" << std::endl;
08}
09 
10void visit_c(CClass& b)
11{
12    std::cout << "visit_c()" << std::endl;
13}
14 
15int _tmain(int argc, _TCHAR* argv&#91;&#93;)
16{
17    AClass a;
18    BClass b;
19    CClass c;
20    Base base;
21 
22    typedef boost::mpl::vector<aclass, BClass, CClass> type_vector;
23 
24    generic_visitor<type_vector> gv;
25 
26    boost::function< void (AClass&) > vf(&visit_a);
27    gv.register_visitor<aclass>(vf); // visit함수 등록!
28    gv.register_visitor<bclass>(&visit_b); // visit함수 등록!
29    gv.register_visitor<cclass>(&visit_c); // visit함수 등록!
30 
31    gv.visit(a); // visit_a()가 콘솔에 출력되어야함
32    gv.visit(b); // visit_b()가 콘솔에 출력되어야함
33    gv.visit(c); // visit_c()가 콘솔에 출력되어야함
34 
35    return 0;
36}

boost::function을 함수타입으로 쓰고 있으므로, boost::bind를 하건, operator()를 써서 만들건, C타입의 함수이건 멤버함수이건 상관없이 쓸 수 있다! 게다가, 서로 다른 클래스에 있는 녀석들도 한데 묶어서 한 Visitor로 처리할 수도 있고, 한 타입에 대해 여러 visit함수를 사용할수도 있다!

실은, 컴파일 타임에 visit함수자를 반드시 등록하도록 강제하고 싶었지만, 그렇게 할 경우 유연성이 너무 떨어질 것 같아서 하지 못했다. 아참. 그리고, 이 방법의 문제점은 type_vector에 있는 클래스가 너무 많을 경우, 컴파일러가 죽을수도 있다. (대충 50개 이상?) 타입 이름이 너무 길고 구조가 복잡하면, MSVC 컴파일러는 종종 죽어버린다.. 🙁

+1. 사실 아이디어는 간단한데 구현하느라 삽질을 좀 했다. template function를 쓰면 간단하긴 한데, 문제는 template function은 바인드가 잘 안된다.. GG
+2. mpl::fold를 써서 후다닥 하려고 했지만, using을 쓰는 등의 문제점이 있어서 포기.

글쓴이: crowmania

Chief Developer in Somansa Guitar/Vocal in Tonics Member of Justice Party

“generic_visitor: Visitor의 일반적인 함수자를 이용한 구현.”의 3개의 생각

  1. 잘 봤습니다. ^^
    저도 dynamic visitor 2 만들면서는 비슷한 방법을 사용했는데 저는 mpl::inherit_linearly 를 사용했습니다.
    그리고 저는 함수 포인터 – Ret (*)(T*) – 를 사용했는데 boost::function을 사용하셔서 더 범용적인것 같네요.
    그런데 generic_visitor_ 랑 visit_unit이 굳이 따로 있을 필요가 없을 것 같기도 하네요.
    그리고 mpl을 과다하게 쓰다 보면 g++도 가끔 죽어요. >.

  2. mpl::inherit_linearly가 있는줄 알았으면 사용했을텐데.. 미처 몰랐네요.. T_T
    그걸 알았다면.. visit_unit을 사용할 필요도 없었을텐데.. 다시 만들어봐야겠네요.

    g++도 죽는군요. 허허. (조만간 g++과 살아야할듯.. 하아)

  3. mpl::inherit_linearly를 사용해 보았는데.. 역시나 using parent::visit부분이 문제가 되는군요.

    generic_visitor 클래스에 template void visit(T& t){}와 같은 함수를 만들고 여기서 static_cast*>(this); 를 해주면 되긴 하지만, 이렇게 할 경우 boost::bind가 잘 되지 않는다는 문제가 있습니다.

    굉장히 지저분하지만 어쩔수 없는걸까요.. 하아.

    아니면 mpl::inherit를 상속받아서 처리하는 방법이 있을지도.. 음. (실험중입니다. ^^)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다