為了支援 C++ 的多态性,才有了動态綁定和靜态綁定。了解他們的差別有助于更好的了解多态性,以及在程式設計的過程中避免犯錯誤。
需要了解四個名詞:
- 對象的靜态類型:對象在聲明時所采用的類型,是在編譯期确定的;
- 對象的動态類型:目前所指對象的類型,是在運作期決定的。對象的動态類型可以更改,但是靜态類型無法更改;
關于對象的靜态類型和動态類型,看如下示例:
class B {};
class C : public B {};
class D : public B {};
D* pD = new D(); //pD的靜态類型是它聲明的類型D*,動态類型也是D*
B* pB = pD; //pB的靜态類型是它聲明的類型B*,動态類型是pB所指向的對象pD的類型D*
C* pC = new C();
pB = pC; //pB的動态類型是可以更改的,現在它的動态類型是C*
- 靜态綁定:綁定的是對象的靜态類型,某特性(比如函數)依賴于對象的靜态類型,發生在編譯期,又稱前期綁定 early binding;
- 動态綁定:綁定的是對象的動态類型,某特性(比如函數)依賴于對象的動态類型,發生在運作期,又稱後期綁定 late binding;
class B {
void DoSomething();
virtual void vfun();
};
class C : public B {
void DoSomething();
//說明一下,這個子類重新定義了父類的non-virtual函數,
//這是一個不好的設計,會導緻名稱遮掩;這裡隻是為了說明動态綁定和靜态綁定才這樣使用。
virtual void vfun();
};
class D : public B {
void DoSomething();
virtual void vfun();
};
D* pD = new D();
B* pB = pD;
雖然 p D pD pD 和 p B pB pB 都指向同一個對象,
pD->DoSomething()
和
pB->DoSomething()
調用的不是同一個函數。原因如下:
① 函數
DoSomething
是一個 n o n − v i r t u a l non-virtual non−virtual 函數,它是靜态綁定的,也就是編譯器會在編譯期根據對象的靜态類型來選擇函數;
② p D pD pD 的靜态類型是 D ∗ D* D∗,那麼編譯器在處理
pD->DoSomething()
的時候會将它指向
D::DoSomething()
;
③ 同理, p B pB pB 的靜态類型是 B ∗ B* B∗,那
pB->DoSomething()
調用的就是
B::DoSomething()
;
但是
pD->vfun()
和
pB->vfun()
調用的是同一個函數。原因如下:
vfun()
是一個 v i r t u a l virtual virtual 函數,它動态綁定的,也就是說它綁定的是對象的動态類型, p B pB pB 和 p D pD pD 雖然靜态類型不同,但是他們同時指向一個對象,他們的動态類型是相同的,都是 D ∗ D* D∗,是以,他們的調用的是同一個函數:
D::vfun()
;
上面都是針對對象指針的情況,對于引用(reference)的情況同樣适用。
指針和引用的動态類型和靜态類型可能會不一緻,但是對象的動态類型和靜态類型是一緻的
D D;
D.DoSomething();
D.vfun();
//以上兩個函數永遠調用的都是D::DoSomething()和D::vfun()
至于哪些是動态綁定,哪些是靜态綁定:隻有虛函數才使用的是動态綁定,其他的全部是靜态綁定。
特别需要注意的地方
class B {
virtual void vfun(int i = 10);
};
class D : public B {
virtual void vfun(int i = 20);
};
D* pD = new D();
B* pB = pD;
pD->vfun();
pB->vfun();
當預設參數和虛函數一起出現的時候情況有點複雜,極易出錯。那就是虛函數是動态綁定的,但是為了執行效率,預設參數是靜态綁定的。
有上面的分析可知
pD->vfun()
和
pB->vfun()
調用都是函數
D::vfun()
,但是他們的預設參數是多少?
分析一下,預設參數是靜态綁定的,
pD->vfun()
時, p D pD pD 的靜态類型是 D ∗ D* D∗,是以它的預設參數應該是20;同理,
pB->vfun()
的預設參數應該是10。編寫代碼驗證了一下,正确。
該特性的存在是因為為了提高運作期效率。如果預設值是動态綁定的,編譯器就必須有某種方法在運作期為 virtual 函數決定适當的預設值。這比目前實行的 “在編譯期決定” 的機制更慢且更複雜。為了程式的執行速度和編譯器實作上的簡易度, c++選擇了這樣的實作方式,就是為了高執行效率。
對于這個特性,估計沒有人會喜歡。是以,永遠記住:條款37:絕不重新定義繼承而來的預設參數(Never redefine function’s inherited default parameters value.)
參考資料:
- 《C++ Primer》
- 《Effective C++》
- 深入了解C++的動态綁定和靜态綁定
- C++之多态性
- C++虛函數和虛函數表原理