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

그래서 했다.

다운로드: generic_visitor.cpp

핵심 클래스는 visit_unit이다.

template<class Visitable>
struct visit_unit
{
public:
	typedef typename boost::function<void (Visitable&)> visit_function;

	void register_visitor(visit_function func){...}
	void visit(Visitable& visitable){...}
};

struct default_generic_visitor
{
public:
	void register_visitor(){}
	void visit(){}
};

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

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

template<class visitable_list>
struct generic_visitor_
	: public boost::mpl::if_c<
	boost::mpl::size<visitable_list>::type::value != 1,
	generic_visitor_<typename boost::mpl::pop_front<visitable_list>::type>,
	default_generic_visitor
	>::type
	, public visit_unit<typename boost::mpl::front<visitable_list>::type>
{
	using visit_unit<typename boost::mpl::front<visitable_list>::type>::visit;

	typedef typename boost::mpl::if_c<
		boost::mpl::size<visitable_list>::type::value != 1,
		generic_visitor_<typename boost::mpl::pop_front<visitable_list>::type>,
		default_generic_visitor
	>::type parent;

	// 다중상속 관계때문에 함수를 빌려와야한다.
	using parent::visit;
};

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

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

template<class visitable_list>
struct generic_visitor
	: generic_visitor_<visitable_list>
{
	template<class T, class F>
		void register_visitor(F f)
	{
		boost::function<void (T& t)> func(f);
		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 클래스 템플릿에 구현이 되어있으며, 나머지는 저 상속관계를 자동으로 만들어주기 위한 코드일 뿐이다.

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

void visit_a(AClass& a)
{
	std::cout << "visit_a()" << std::endl;
}
void visit_b(BClass& b)
{
	std::cout << "visit_b()" << std::endl;
}

void visit_c(CClass& b)
{
	std::cout << "visit_c()" << std::endl;
}

int _tmain(int argc, _TCHAR* argv&#91;&#93;)
{
	AClass a;
	BClass b;
	CClass c;
	Base base;

	typedef boost::mpl::vector<aclass, BClass, CClass> type_vector;

	generic_visitor<type_vector> gv;

	boost::function< void (AClass&) > vf(&visit_a);
	gv.register_visitor<aclass>(vf); // visit함수 등록!
	gv.register_visitor<bclass>(&visit_b); // visit함수 등록!
	gv.register_visitor<cclass>(&visit_c); // visit함수 등록!

	gv.visit(a); // visit_a()가 콘솔에 출력되어야함
	gv.visit(b); // visit_b()가 콘솔에 출력되어야함
	gv.visit(c); // visit_c()가 콘솔에 출력되어야함

	return 0;
}

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

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

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

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

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

  • 2006/10/13 3:05 오후
    고유주소

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

    응답
  • 2006/10/14 12:03 오전
    고유주소

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

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

    응답
  • 2006/10/14 1:05 오전
    고유주소

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

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

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

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

    응답

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.