까막님의 downcast_overloader 코드를 보고 이런 코드가 어떤 경우에 쓸모가 있을까 생각하다가 만들어 본 내용입니다.
먼저 다음과 같이 클래스들이 있다고 가정합니다.
이 클래스들에 polymorphic하게 자기 클래스의 이름을 print하는 기능을 추가하는 방법에 대해 살펴보겠습니다.
먼저 위 클래스들이 있는 코드를 자유롭게 수정할 수 있다면 다음과 같이 virtual 함수를 추가하면 됩니다.
위 함수를 사용하는 방법은 다음과 같습니다.
너무 간단하죠? :-) 그럼 다음으로 이 클래스들이 있는 소스 코드를 수정할 수 없는 경우를 알아보겠습니다. 이런 경우에는 라이브러리 설계자들이 자신이 만든 클래스의 코드 수정 없이 사용자에 의한 확장이 가능하도록 하기 위해 Visitor 패턴을 지원할 가능성이 높습니다. 이 경우 B, D1, D2 클래스는 다음과 같이 되어 있을 것입니다.
여기에 print 기능을 추가하려면 Visitor를 상속받아 필요한 함수를 다음과 같이 구현하면 됩니다. 코드의 아래 부분에 사용 방법이 있습니다.
그럼 이제 마지막으로 코드를 수정할 수도 없고 Visitor 패턴도 제공되지 않는 경우를 알아보겠습니다. 이 경우에 사용할 수 있는 것이 바로 이번 글에서 작성할 dynamic_visitor입니다. 이 클래스는 dynamic_cast를 사용하여 실제 runtime 타입을 알아냅니다. 먼저 사용법은 다음과 같습니다.
위에서 봤던 일반 Visitor와 사용법이 거의 유사함을 알 수 있습니다. 한가지 규칙은 타입들을 리스트할 때 가장 하위의(derived) 클래스가 제일 앞에 오도록 하고 마지막 타입은 base 클래스의 타입을 적어야 한다는 것입니다. ((실제 코드에선 BOOST_MPL_ASSERT를 사용하여 이 규칙이 지켜지지 않으면 컴파일이 되지 않도록 되어 있습니다.)) 그럼 코드를 살펴보겠습니다.
(1)번 라인의 구현을 위해 CRTP 패턴을 사용했습니다. 그리고 (2)번은 현재 그냥 에러를 출력하도록 했는데 정상적인 경우 절대 호출될 수 없으므로 assert()처리해도 무방합니다. ((Type List의 마지막이 base 클래스이므로 구현하지 않은 다른 derived 클래스가 있다고 하더라도 마지막의 base* 에 의해 처리되기 때문입니다.)) 다른 코드는 별로 어려운 것이 없어 보입니다.
실제 코드는 아래에서 다운로드 받을 수 있습니다.
dynamic_visitor.zip
먼저 다음과 같이 클래스들이 있다고 가정합니다.
struct B {}
struct D1 : B {}
struct D2 : B {}
이 클래스들에 polymorphic하게 자기 클래스의 이름을 print하는 기능을 추가하는 방법에 대해 살펴보겠습니다.
먼저 위 클래스들이 있는 코드를 자유롭게 수정할 수 있다면 다음과 같이 virtual 함수를 추가하면 됩니다.
struct B
{
virtual void print() const { cout << "Bn"; }
};
struct D1 : B
{
void print() const { cout << "D1n"; }
};
struct D2 : B
{
void print() const { cout << "D2n"; }
};
위 함수를 사용하는 방법은 다음과 같습니다.
B* b1 = new D1;
B* b2 = new D2;
b1->print();
b2->print();
너무 간단하죠? :-) 그럼 다음으로 이 클래스들이 있는 소스 코드를 수정할 수 없는 경우를 알아보겠습니다. 이런 경우에는 라이브러리 설계자들이 자신이 만든 클래스의 코드 수정 없이 사용자에 의한 확장이 가능하도록 하기 위해 Visitor 패턴을 지원할 가능성이 높습니다. 이 경우 B, D1, D2 클래스는 다음과 같이 되어 있을 것입니다.
struct Visitor
{
virtual void visit(B* b) {}
virtual void visit(D1* b) {}
virtual void visit(D2* b) {}
};
struct B
{
virtual void accept(Visitor* v) { v->visit(this); }
};
struct D1 : B
{
void accept(Visitor* v) { v->visit(this); }
};
struct D2 : B
{
void accept(Visitor* v) { v->visit(this); }
};
여기에 print 기능을 추가하려면 Visitor를 상속받아 필요한 함수를 다음과 같이 구현하면 됩니다. 코드의 아래 부분에 사용 방법이 있습니다.
struct PrintVisitor : Visitor
{
void visit(B* b) { cout << "Bn"; }
void visit(D1* b) { cout << "D1n"; }
void visit(D2* b) { cout << "D2n"; }
};
// usage
PrintVisitor pv;
b1->accept(&pv);
b2->accept(&pv);
그럼 이제 마지막으로 코드를 수정할 수도 없고 Visitor 패턴도 제공되지 않는 경우를 알아보겠습니다. 이 경우에 사용할 수 있는 것이 바로 이번 글에서 작성할 dynamic_visitor입니다. 이 클래스는 dynamic_cast를 사용하여 실제 runtime 타입을 알아냅니다. 먼저 사용법은 다음과 같습니다.
typedef mpl::vector TL;
struct PrintVisitor : dynamic_visitor
{
void visit(B* b) { cout << "Bn"; }
void visit(D1* b) { cout << "D1n"; }
void visit(D2* b) { cout << "D2n"; }
};
// usage
PrintVisitor pv;
pv(b1);
pv(b2);
위에서 봤던 일반 Visitor와 사용법이 거의 유사함을 알 수 있습니다. 한가지 규칙은 타입들을 리스트할 때 가장 하위의(derived) 클래스가 제일 앞에 오도록 하고 마지막 타입은 base 클래스의 타입을 적어야 한다는 것입니다. ((실제 코드에선 BOOST_MPL_ASSERT를 사용하여 이 규칙이 지켜지지 않으면 컴파일이 되지 않도록 되어 있습니다.)) 그럼 코드를 살펴보겠습니다.
template
class dynamic_visitor
{
typedef typename mpl::back::type base_type;
public:
void operator()(base_type* b) {
return do_visit(b, mpl::false_());
}
private:
template
void do_visit(base_type* b, mpl::false_) {
typedef typename mpl::front::type Head;
typedef typename mpl::pop_front::type Tail;
typedef typename mpl::empty::type IsEmpty;
Head* p = dynamic_cast(b);
if (p != 0) {
return static_cast(this)->visit(p); // (1)
}
return do_visit(b, IsEmpty());
}
template
void do_visit(base_type* b, mpl::true_) {
cout << "assertn"; // handle error. // (2)
}
};
(1)번 라인의 구현을 위해 CRTP 패턴을 사용했습니다. 그리고 (2)번은 현재 그냥 에러를 출력하도록 했는데 정상적인 경우 절대 호출될 수 없으므로 assert()처리해도 무방합니다. ((Type List의 마지막이 base 클래스이므로 구현하지 않은 다른 derived 클래스가 있다고 하더라도 마지막의 base* 에 의해 처리되기 때문입니다.)) 다른 코드는 별로 어려운 것이 없어 보입니다.
실제 코드는 아래에서 다운로드 받을 수 있습니다.
Downloads
dynamic_visitor.zip
[...] 이원구님의 C++ of the Day #33 - dynamic visitor를 읽다가 문득 생각이 났다. Visitor를 generic하게 구현할 수 있지 않을까??? 그래서 했다. [...]
ReplyDelete