在C++語言中有一組基礎的概念一直都容易混淆:Overload、Override和Overwrite分別表示什么意思?下面把這三個概念整理一下:
1. Overload(重載)
重載的概念最好理解,在同一個類聲明范圍中,定義了多個名稱完全相同、參數(類型或者個數)不相同的函數,就稱之為Overload(重載)。重載的特征如下:
(1)相同的范圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual 關鍵字可有可無。
2. Override(覆蓋)
覆蓋的概念其實是用來實現C++多態性的,即子類重新改寫父類聲明為virtual的函數。Override(覆蓋)的特征如下:
(1)不同的范圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數列表完全相同;
(4)基類函數必須有virtual 關鍵字。
3. Overwrite(改寫)
改寫是指派生類的函數屏蔽(或者稱之為“隱藏”)了與其同名的基類函數。正是這個C++的隱藏規則使得問題的復雜性陡然增加,這里面分為兩種情況討論:
(1)如果派生類的函數與基類的函數同名,但是參數不同。那么此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。那么此時,基類的函數被隱藏(注意別與覆蓋混淆)。
借鑒一個網上的例子來看Overwrite(改寫)的情況:
#include <iostream> using namespace std; class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } virtual void g(float x){ cout << "Base::g(float) " << x << endl; } void h(float x){ cout << "Base::h(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } virtual void g(int x){ cout << "Derived::g(int) " << x << endl; } void h(float x){ cout << "Derived::h(float) " << x << endl; } }; int main() { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 (surprise!) pd->g(3.14f); // Derived::g(int) 3 // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 return 0; }
在上面這個例子中:
- 函數Derived::f(float)覆蓋(override)了Base::f(float)。
- 函數Derived::g(int)改寫/隱藏(overwrite)了Base::g(float)。
- 函數Derived::h(float)改寫/隱藏(overwrite)了Base::h(float)。
4. 特殊情況說明
除了上面講到的三種情況之外,還有一些比較容易迷惑的地方,例如:
4.1 同名的普通函數與const函數本質上是兩個不同的函數,應該等價理解為這兩個同名函數的參數是不同的。在派生類中的virtual函數理解上可能會有誤解。
參見如下例子:
#include <iostream> using namespace std; class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x) const { cout << "Derived::f(float) " << x << endl; } }; int main() { Derived d; Base *pb = &d; Derived *pd = &d; // Bad : behavior depends solely on type of the object pb->f(3.14f); // Base::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 return 0; }
4.2 基類中定義的virtual虛函數,在繼承子類中同名函數自動都屬於虛函數,可以不需要virtual關鍵字。
4.3 如果基類中定義的函數不是virtual,而子類中又將相同函數定義為virtual,則稱之為越位,函數行為依賴於指針/引用的類型,而不是實際對象的類型。
參見如下例子:
#include<iostream> using namespace std; class Base { public: void f(){ cout << "Base::f() " << endl; } virtual void g(){ cout << "Base::g() " << endl; } }; class Derived : public Base { public: virtual void f(){ cout << "Derived::f() " << endl; } void g(){ cout << "Derived::g() " << endl; } }; class VirtualDerived : virtual public Base { public: void f(){ cout << "VirtualDerived::f() " << endl; } void g(){ cout << "VirtualDerived::g() " << endl; } }; int main() { Base *d = new Derived; Base *vd = new VirtualDerived; d->f(); // Base::f() Bad behavior d->g(); // Derived::g() vd->f(); // Base::f() Bad behavior vd->g(); // VirtualDerived::g() delete d; delete vd; return 0; }
5. 針對非虛函數的繼承說明
在《Effective C++》中講述了這樣一個規則:任何條件下都要禁止重新定義繼承而來的非虛函數。
公有繼承的含義是 "是一個"(is a),"在一個類中聲明一個非虛函數實際上為這個類建立了一種特殊性上的不變性"。如果將這些分析套用到類B、類D和非虛成員函數B::mf,那么:
(1)適用於B對象的一切也適用於D對象,因為每個D的對象“是一個”B的對象。
(2)B的子類必須同時繼承mf的接口和實現,因為mf在B中是非虛函數。
那么,如果D重新定義了mf,設計中就會產生矛盾。如果D真的需要實現和B不同的mf,而且每個B的對象(無論怎么特殊)也真的要使用B實現的mf,那么每個D將不 "是一個" B。這種情況下,D不能從B公有繼承。相反,如果D真的必須從B公有繼承,而且D真的需要和B不同的mf的實現,那么,mf就沒有為B反映出特殊性上的不變性。這種情況下,mf應該是虛函數。最后,如果每個D真的 "是一個" B,並且如果mf真的為B建立了特殊性上的不變性,那么,D實際上就不需要重新定義mf,也就決不能這樣做。
不管采用上面的哪一種論據都可以得出這樣的結論:任何條件下都要禁止重新定義繼承而來的非虛函數。
