Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

Pure Software Engineer :)

[C++] 더블 디스패치 (double dispatch) 본문

Software Engineering/Programming

[C++] 더블 디스패치 (double dispatch)

HelloJaewon 2014. 10. 25. 14:12

C++의 다형성(polymorphism)은 객체의 동작방식을 런타임(runtime)에 결정하도록 한다.

 

예를들어, Animal 클래스에 move() 함수가 있다면, 물고기는 헤엄, 강아지는 달리기, 새는 날아서 움직일 것이다.

이와 같은 실제 동물에 따른 행위가 컴파일시점에 결정하는것이 아니라 실행시간에 결정할 수 있다는것이 다형성의 개념이다.

 

하지만, 경우에 따라서 런타임때 두가지 이상의 타입에 따라 함수의 동작방식이 선택되야 할 경우가 있다.

예를들면, Animal 클래스에 어떤 동물이 다른 동물을 먹을수 있는지 반환하는 eats() 함수가 있다고 하자.

이때는 먹는동물과 먹히는 동물에 따라 eats의 결과가 달라져야 할 것이다.

 

C++에서 언어차원에서 두개 이상의 타입을 런타임에 결정할수있는 방법을 제공하지는 않는다.

하지만, 지금 설명하는 더블 디스패치(double dispatch)라는 방법을 사용하면 두 개 이상의 객체에 연동되는 가상함수를 만들 수 있다.

 

먼저, 오버로딩을 이용한 1차원 다형성을 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
 
using namespace std;
 
class Fish;
class Bear;
 
class Animal {
public:
    virtual bool eats (const Bear & inPrey) const = 0;
    virtual bool eats (const Fish & inPrey) const = 0;
};
 
class Fish : public Animal {
public:
    virtual bool eats (const Bear & inPrey) const { return false; }
    virtual bool eats (const Fish & inPrey) const { return true; }
};
 
class Bear : public Animal {
public:
    virtual bool eats (const Bear & inPrey) const { return true; }
    virtual bool eats (const Fish & inPrey) const { return true; }
};
 
int main () {
    Bear myBear;
    Fish myFish;
    Animal & animalRef = myBear;
    cout<<animalRef.eats(myFish)<<endl; // polymorphism
    cout<<myFish.eats(animalRef)<<endl; // compile error
 
    return 0;
}

 

위의 코드에서 30번째 라인은 다형성을 이용해서 컴파일 및 실행까지 잘 동작하는 코드이다.

하지만, 31번째 라인은 컴파일시 에러가 발생한다. 왜냐하면 animalRef는 Animal을 가리키는 Reference인데, Fish 클래스에는 Animal을 인자로 받는 eats 함수가 없다.

31번째 라인은, 어느 클래스에서 함수를 호출할지 결정하는 것(다형성)이 아니라, 오버로딩된 함수 중에서 어느 함수를 호출할 것인가를 결정하려고 하기 때문이다.

 

더블 디스패치를 보자.

더블 디스패치는 함수호출시 전달하는 파라미터를 통해 호출할 함수를 결정하도록 한다.

예를들어, Animal 클래스에 eatenBy() 함수가 있고, 이 함수는 Animal 객체를 받아서 현재 Animal이 대상 Animal에게 먹히면 true를 반환한다고 하자.

 

그렇다면 eats 함수는 다음과 같이 구현할 수 있다.

 

 
1
2
3
bool Fish::eats (const Animal & isPrey) const {
    return isPrey.eatenBy(*this);
}

 

eatenBy 함수가 생기면서 먼가 더 복잡해 보인다.

여기서 중요한 점은 eats 함수를 호출할때 다형성이 2번 발생한다는 것이다.

첫째로, 어떤 클래스의 eats 함수를 호출하느냐이고

둘째는, eats함수의 인자로 전달받은 isPrev를 통해 어떤 동물의 eatenBy 함수를 호출하느냐이다.

이것을 결정하는것이 모두 런타임때 결정된다는 것이다.

 

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
 
using namespace std;
 
class Fish;
class Bear;
 
//===========================================================================
class Animal {
public:
    virtual bool eats (const Animal & isPrey) const = 0;
    virtual bool eatenBy (const Fish & inFish) const = 0;
    virtual bool eatenBy (const Bear & inBear) const = 0;
};
 
//===========================================================================
class Fish : public Animal {
public:
    virtual bool eats (const Animal & isPrey) const;
    virtual bool eatenBy (const Fish & inFish) const;
    virtual bool eatenBy (const Bear & inBear) const;
};
 
//===========================================================================
class Bear : public Animal {
public:
    virtual bool eats (const Animal & isPrey) const;
    virtual bool eatenBy (const Fish & inFish) const;
    virtual bool eatenBy (const Bear & inBear) const;
};
 
//===========================================================================
bool Fish::eats (const Animal & isPrey) const {
    return isPrey.eatenBy(*this);
}
 
bool Fish::eatenBy (const Fish & inFish) const {
    return true;
}
 
bool Fish::eatenBy (const Bear & inBear) const {
    return true;
}
 
//===========================================================================
bool Bear::eats (const Animal & isPrey) const {
    return isPrey.eatenBy(*this);
}
 
bool Bear::eatenBy (const Fish & inFish) const {
    return false;
}
 
bool Bear::eatenBy (const Bear & inBear) const {
    return false;
}
 
//===========================================================================
int main () {
    Bear myBear;
    Fish myFish;
    Animal & animalRef = myBear;
    cout<<animalRef.eats(myFish)<<endl; // polymorphism
    cout<<myFish.eats(animalRef)<<endl; // good!
 
    return 0;
}

 

아까와 달리 main 함수는 컴파일 및 실행까지 정상적으로 동작한다.

 

아래 main 함수를 보자.

 

 
1
2
3
4
5
6
7
8
9
10
//===========================================================================
int main () {
    Bear myBear;
    Fish myFish;
    Animal & animal1 = myBear;
    Animal & animal2 = myFish;
    cout<<animal1.eats(animal2)<<endl;
 
    return 0;
}

 

7번째 라인에서 2번의 다형성이 발생한다.

먼저 어느 클래스의 eats 함수를 호출할지 결정하고,

eats 함수 내부에서는 animal2 인자를 통해 어느 클래스의 eatenBy 함수를 호출할지 결정한다.

 

여기서 주의할 점은 eats 함수의 구현이 Fish, Bear클래스 모두 동일한데, 그렇다고 부모 클래스인 Animal 클래스에 구현을 해서는 안된다.

왜냐하면, eatenBy 함수는 함수 오버로딩으로 구현하고 있는데, Animal 타입을 받는 함수는 존재하지 않기 때문이다.

앞에서 잠깐 얘기했듯이, 함수 오버로딩은 런타임때 결정되는것이 아니라 컴파일 타임에 결정된다.

 

C++ Open source project를 찾다가 우연히 yomm11 라는 것을 보았는데, C++로 구현하기 까다로운 double dispatch를 multi-method라는 개념을 이용해서 쉽게 구현할 수 있도록 하는 라이브러리인듯 하다.

시간나면 좀 더 읽어봐야겠다.

 

 

References

 - 전문가를 위한 C++ 2권

 - http://www.yorel.be/mm/multi_methods/introduction.html

'Software Engineering > Programming' 카테고리의 다른 글

[Item 7] Distinguising between () and {} when creating objects  (0) 2014.12.05
Effective Modern C++  (0) 2014.12.05
[C++] bool vs. enum  (0) 2014.08.11
[Refactoring] What is refactoring?  (0) 2014.05.17
[C] namespace  (0) 2014.01.29