--
在談 Double Dispatch 之前我們必需先從 Polymorphism 說起。Polymorphism 有兩種:
- Subtype Polymorphism: 前者就是指繼承類別之間的多型。例如:人和狗同時繼承動物類別,二者都會進食,不過二者的吃飯方式不同。我們會在動物宣告一個 Virtual Method,人和狗類別會各自 Override 自己的吃飯方式,利用 Virtual Method Invocation,在執行期喚起正確的函式。 (Method Overriding)
- Type Polymorphism (Ad-hoc Polymorphism): 有一個同名的函式,編譯器會依據傳入參數的不同,選擇不同的函式,例如:同樣是 + operator,你把數字加數字與字串加字串就會有不同的結果。(Function Overloading)
我們先看一下什麼是 Type Polymorphism (Function Overloading):
class A;
class B;
class A {
public:
void interact(A const &a) { cout << "class A vs A" << endl; }
void interact(B const &b) { cout << "class A vs B" << endl; }
};
class B : public A {
public:
void interact(A const &a) { cout << "class B vs A" << endl; }
void interact(B const &b) { cout << "class B vs B" << endl; }
};
int main() {
A a;
B b;
a.interact(a); // Invoke A::interact(A const&)
a.interact(b); // Invoke A::interact(B const&)
b.interact(a); // Invoke B::interact(A const&)
b.interact(b); // Invoke B::interact(B const&)
return 0;
}
我們可以注意到,透過 Function Overloading,我們可以在不同的時候喚起不同的函式。例如:a.interact(b) 就會喚起 void A::interact(B const &) 這個函式。
如果因為某些原因我必需使用類別 A 的 Reference 來指稱其子類別的 Instance,我把上面的程式改成這樣呢?
int main() {
A &aa = a;
A &ba = b;
a.interact(aa); // Invoke A::interact(A const&)
a.interact(ba); // Invoke A::interact(A const&)
}
我們就會發現 Type Polymorphism 完全幫不了我們!為什麼?因為類別 A 的 Reference 所指向的物件的 Type 是可以隨 Runtime 的不同而有所變動,所以一般來說編譯器只能假定這個 Reference 只會是類別 A 的實例。從而只能喚起 A::interact(A const&) 函式。那我們該怎麼做呢?
此時我們會想聯想的 Subtype Polymorphism (Method Overriding) 的 Late Binding。首先我們要為上述的函式加上 virtual 關鍵字,使其可以動態地決定要喚起哪個函式。我分別 class A, class B 的 interact 函式的前面都加上了 virtual 關鍵字。(雖然一日 Virtual Method 終身 Virtual Method,所以加在 class A 裡面的 Method 就可以了,不過為了方便閱讀,我是建議在 class B 裡面的 Method 也加上 virtual)
class A {
public:
virtual void interact(A const &a) { cout << "class A vs A" << endl; }
virtual void interact(B const &b) { cout << "class A vs B" << endl; }
};
class B : public A {
public:
virtual void interact(A const &a) { cout << "class B vs A" << endl; }
virtual void interact(B const &b) { cout << "class B vs B" << endl; }
};
這下問題解決了嗎?我們看看:
int main() {
aa.interact(aa); // Invoke A::interact(A const&)
aa.interact(ba); // Invoke A::interact(A const&)
}
很顯然地,雖然 interact 是 Virtual Function,但是因為只有 this 會透過 Late Binding 而被延遲到執行期,編譯器還是會在編譯期施行 Method Overloading Resolution 從眾多 Overloaded Method 當中挑選特定版本生成程式碼,讓我們的程式於執行期呼叫 Virtual Method(進而依據 this 所屬類別進行 Dynamic Dispatch),所以這裡 Subtype Polymorphism 似乎幫不上忙。
不過,事實上 Subtype Polymorphism 是可以幫上忙的!只是我們需要一個小技巧!這就是所謂的 Double Dispatching,我們可以再寫一個函式叫作 apply,它固定傳入一個類別 A 的 Reference,不過我們會在函式的本體上動手腳!
class A {
public:
virtual void apply(A &obj) { obj.interact(*this); }
};
class B : public A {
public:
virtual void apply(A &obj) { obj.interact(*this); }
};
int main() {
aa.apply(aa); // Finally invoke A::interact(A const&)
ba.apply(aa); // Finally invoke A::interact(B const&)
aa.apply(ba); // Finally invoke B::interact(A const&)
ba.apply(ba); // Finally invoke B::interact(B const&)
}
對,這裡我用了 *this,*this 的型別是什麼呢?對!就是擁有 apply 函式的類別。注意:我們必需要在每個類別加上相同的 apply 函式。因為每個 apply 函式中的 *this 型別是不一樣的。當我們用 object1.apply(object2) 呼叫時,apply 會幫我們找出 object1 的確切類別,再使用 object2.interact((THISCLASS &)object1) 來呼叫 interact 函式,因為 object1 的型別已經確定,所以第二次呼叫 interact 就是找出 object2 的確切類別,喚起正確的函式。
這大概是 Double Dispatch 的內容。
--
不過壞消息是:這個作業中我們不能碰原有的 class 也就不能為原有的 class 加上 virtual method,所以這樣寫似乎沒有什麼用。