C++的重載(overload)與重寫(override)

成員函數被重載的特徵:
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual關鍵字可有可無。

重寫是指派生類函數重寫基類函數,是C++的多態的表現,特徵是:
(1)不同的範圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual關鍵字。

示例中,函數Base::f(int)與Base::f(float)相互重載,而Base::g(void)被Derived::g(void)重寫。

#include <iostream>
using namespace std;

class Base
{
public:
    void f(int x){ cout << "Base::f(int) " << x << endl; }
    void f(float x){ cout << "Base::f(float) " << x << endl; }
    virtual void g(void){ cout << "Base::g(void)" << endl;}
};

class Derived : public Base
{
public:
    virtual void g(void){ cout << "Derived::g(void)" << endl;}
};

int main()
{
    Derived  d;
    Base *pb = &d;
    pb->f(42);        // Base::f(int) 42
    pb->f(3.14f);     // Base::f(float) 3.14
    pb->g();          // Derived::g(void)

    return 0;
}

令人迷惑的隱藏規則

本來僅僅區別重載與重寫並不算困難,但是C++的隱藏規則(遮蔽現象)使問題複雜性陡然增加。這裏“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏。
這種隱藏規則,不僅僅是表現在對成員函數上,對同名的data member也是如此。

示例程序中:
(1)函數Derived::f(float)重寫了Base::f(float)。
(2)函數Derived::g(int)隱藏了Base::g(float)。
(3)函數Derived::h(float)隱藏了Base::h(float)。

#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;
}

《Effective C++》條款37: 決不要重新定義繼承而來的非虛函數。說明了不能重新定義繼承而來的非虛函數的理論依據是什麼。
以下摘自《Effective C++》:
公有繼承的含義是 "是一個","在一個類中聲明一個非虛函數實際上爲這個類建立了一種特殊性上的不變性"。如果將這些分析套用到類B、類D和非虛成員函數B::mf,那麼:
  ·適用於B對象的一切也適用於D對象,因爲每個D的對象 "是一個" B的對象。
  ·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,也就決不能這樣做。
不管採用上面的哪一種論據都可以得出這樣的結論:任何條件下都要禁止重新定義繼承而來的非虛函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章