Skip to main content

C++ of the Day #33 - dynamic visitor

까막님의 downcast_overloader 코드를 보고 이런 코드가 어떤 경우에 쓸모가 있을까 생각하다가 만들어 본 내용입니다.

먼저 다음과 같이 클래스들이 있다고 가정합니다.


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

Comments

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

    ReplyDelete

Post a Comment

Popular posts from this blog

CodeHighlighter plugin test page.

This post is for testing CodeHighlighter plugin which uses GeSHi as a fontifier engine. ((Those code blocks are acquired from Google Code Search .)) ((For more supported languages, go CodeHighlighter plugin or GeSHi homepage.)) C++ (<pre lang="cpp" lineno="1">) class nsScannerBufferList { public: /** * Buffer objects are directly followed by a data segment. The start * of the data segment is determined by increment the |this| pointer * by 1 unit. */ class Buffer : public PRCList { public: Buffer() { ++index_; } PHP (<pre lang="php" lineno="4">) for ($i = 0; $i $value = ord( $utf8_string[ $i ] ); if ( $value < 128 ) { // ASCII $unicode .= chr($value); } else { if ( count( $values ) == 0 ) { $num_octets = ( $value } $values[] = $value; Lisp (<pre lang="lisp">) ;;; Assignment (define-caller-pattern setq ((:star var fo...

Textiler plugin test page

This post is for testing Textiler plugin . This plugin uses Textile engine (version 2.0.0). The sample text is come from Textile test page. (Note that the result will be vary according to your CSS options.) Supported wiki syntax Rendering result h2{color:green}. This is a title h3. This is a subhead p{color:red}. This is some text of dubious character. Isn't the use of "quotes" just lazy writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote. bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised. Simple list: #{color:blue} one # two # three Multi-level list: # one ## aye ## bee ## see # two ## x ## y # three Mixed list: * Point one * Point two ## Step 1 ## Step 2 ## Step 3 * Point three ** Sub point 1 ** Sub point 2 Well, that went well. How about we insert an <a href="/" title="watch out">old-fashioned hypertext link</a>? Will the quo...

C++ of the Day #43 - SQLite3 C++ wrapper #1

The Definitive Guide to SQLite 를 읽다가 공부 겸 해서 C++ wrapper를 만들어 보았습니다. 최대한 C++ 냄새(?)가 나도록 만들어 보았습니다. :-) ((SQLite는 복잡한 관리가 필요없이 사용가능한, 파일이나 메모리 기반의, 라이브러리로 제공되는, 약 250kb 용량의, 대부분의 SQL92문을 지원하는, open source RDB입니다.)) 이 wrapper를 사용하기 위해서는 (당연하게도!) sqlite3 와 (당연하게도?) boost 라이브러리가 필요합니다. 사용 예들을 살펴보는 것으로 설명을 대신합니다. 이번 글에서는 다음과 같은 contacts 테이블이 test.db에 존재한다고 가정합니다. CREATE TABLE contacts ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, phone TEXT NOT NULL, UNIQUE(name, phone) ); Command 먼저 test.db 파일을 사용하기 위해 다음과 같이 파일 이름을 주어 connection 객체를 생성합니다. 생성과 동시에 test.db와 연결이 이루어집니다. ((생성자외에 open() 함수를 사용할 수도 있습니다.)) sqlite3pp::connection conn("test.db"); 다음은 contacts 테이블에 정보를 추가하는 가장 간단한 방법입니다. connection 클래스에서 제공하는 execute 함수를 사용합니다. ((executef 함수를 사용하면 printf와 같은 문법을 사용하여 query문을 작성할 수 있습니다.)) conn.execute("INSERT INTO contacts (name, phone) VALUES ('user', '1234')"); 위와 동일한 작업을 parameterized query를 사용하여 할 수도 있습니다. ((step()함수가 실제 query문을 수행하는 함수입니다. ...