이원구님의 C++ of the Day #33 – dynamic visitor를 읽다가 문득 생각이 났다. Visitor를 generic하게 구현할 수 있지 않을까???
그래서 했다.
다운로드: generic_visitor.cpp
핵심 클래스는 visit_unit이다.
01 | template < class Visitable> |
05 | typedef typename boost::function< void (Visitable&)> visit_function; |
07 | void register_visitor(visit_function func){...} |
08 | void visit(Visitable& visitable){...} |
11 | struct default_generic_visitor |
14 | void register_visitor(){} |
대략 위와 같은 구조를 하고 있는데, visit_unit은 Visitable클래스에 대한 함수자를 vector로 관리해주는 녀석이고, default_generic_visitor는 연쇄상속기법을 사용하기 위해 만든 마지막 Tail클래스다.
연쇄상속기법을 이용하는 코드는 다음과 같다. (ㄷㄷㄷ)
01 | template < class visitable_list> |
02 | struct 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 |
08 | , public visit_unit< typename boost::mpl::front<visitable_list>::type> |
10 | using visit_unit< typename boost::mpl::front<visitable_list>::type>::visit; |
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 |
boost::mpl을 많이 빌려왔다..
generic_visitor_는 일종의 구현부인데, 자신이 보고있는 visitable_list의 크기가 1이면 default_generic_visitor를 상속받고, 아니면 visitable_list의 제일 앞놈을 뺀 generic_visitor_를 상속받는다. 또한, visit()등의 함수를 실제로 구현하는 visit_unit을 별도로 상속받는다.
그리고, generic_visit_에 의해 생성된 상속트리를 generic_visitor가 상속받는다.
01 | template < class visitable_list> |
03 | : generic_visitor_<visitable_list> |
05 | template < class T, class F> |
06 | void register_visitor(F f) |
08 | boost::function< void (T& t)> func(f); |
09 | static_cast <visit_unit<t>*>( this )->register_visitor(f); |
이녀석은 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 클래스 템플릿에 구현이 되어있으며, 나머지는 저 상속관계를 자동으로 만들어주기 위한 코드일 뿐이다.
실제로 쓰려면 다음과 같다.
01 | void visit_a(AClass& a) |
03 | std::cout << "visit_a()" << std::endl; |
05 | void visit_b(BClass& b) |
07 | std::cout << "visit_b()" << std::endl; |
10 | void visit_c(CClass& b) |
12 | std::cout << "visit_c()" << std::endl; |
15 | int _tmain( int argc, _TCHAR* argv[]) |
22 | typedef boost::mpl::vector<aclass, BClass, CClass> type_vector; |
24 | generic_visitor<type_vector> gv; |
26 | boost::function< void (AClass&) > vf(&visit_a); |
27 | gv.register_visitor<aclass>(vf); |
28 | gv.register_visitor<bclass>(&visit_b); |
29 | gv.register_visitor<cclass>(&visit_c); |
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
crowmania의 모든 글 보기
잘 봤습니다. ^^
저도 dynamic visitor 2 만들면서는 비슷한 방법을 사용했는데 저는 mpl::inherit_linearly 를 사용했습니다.
그리고 저는 함수 포인터 – Ret (*)(T*) – 를 사용했는데 boost::function을 사용하셔서 더 범용적인것 같네요.
그런데 generic_visitor_ 랑 visit_unit이 굳이 따로 있을 필요가 없을 것 같기도 하네요.
그리고 mpl을 과다하게 쓰다 보면 g++도 가끔 죽어요. >.
mpl::inherit_linearly가 있는줄 알았으면 사용했을텐데.. 미처 몰랐네요.. T_T
그걸 알았다면.. visit_unit을 사용할 필요도 없었을텐데.. 다시 만들어봐야겠네요.
g++도 죽는군요. 허허. (조만간 g++과 살아야할듯.. 하아)
mpl::inherit_linearly를 사용해 보았는데.. 역시나 using parent::visit부분이 문제가 되는군요.
generic_visitor 클래스에 template void visit(T& t){}와 같은 함수를 만들고 여기서 static_cast*>(this); 를 해주면 되긴 하지만, 이렇게 할 경우 boost::bind가 잘 되지 않는다는 문제가 있습니다.
굉장히 지저분하지만 어쩔수 없는걸까요.. 하아.
아니면 mpl::inherit를 상속받아서 처리하는 방법이 있을지도.. 음. (실험중입니다. ^^)