1. 程式人生 > 其它 >Effective C++條款33:繼承與面向物件——避免遮掩繼承而來的名字

Effective C++條款33:繼承與面向物件——避免遮掩繼承而來的名字

技術標籤:effective c++c++

一、變數隱藏使用規則

  • 當全域性和區域性存在相同的變數時,在區域性作用域中,全域性作用域的變數名會被隱藏,優先使用區域性的變數
  • 例如:
int x; //全域性變數


void someFunc()

{

    double x; //區域性變數

    std::cin >> x; //區域性變數賦值

]

二、繼承中的隱藏與重寫(覆蓋)

  • 當基類中定義的變數/成員函式,派生類也定義同名的時,基類的變數/成員函式就會在派生類中被隱藏

演示案例

class Base

{

private:

    int x;

public:

    virtual void mf1() = 0;

    virtual void mf2();

    void mf3();

};


class Derived :public Base

{

public:

    virtual void mf1(); //重寫(覆蓋)

    void mf4(){

    fm2(); //呼叫基類中的fm2

    }

};
  • 上面類的關係如下:

  • 在Derived的fm4()函式中呼叫了fm2()函式,對於fm2()函式的查詢順序如下:
    • 先在fm4()函式中查詢,如果沒有進行下一步
    • 然後在Derived類中查詢,如果沒有進行下一步
    • 然後在基類Base中查詢(查詢到了就呼叫基類中的Base)
    • 假設在Base中還沒有查詢到,那麼就在Base所在的namespace中查詢;如果還沒有繼續在全域性作用域查詢

三、隱藏基類的全部函式

  • 如果基類中含有一些列過載函式,只要派生類定義了一個與基類同名的函式,那麼基類中的所有過載函式對於派生類來說全都被隱藏(即使引數列表不一致也是)
  • 設計規則的原因:防止你在程式庫或應用框架內建立新的派生類時附帶地從疏遠的基類繼承過載函式

演示案例

class Base

{

private:

    int x;

public:

    virtual void mf1() = 0;

    virtual void mf1(int);

    virtual void mf2();

    void mf3();

    void mf3(double);

};


class Derived :public Base

{

public:

    virtual void mf1(); //基類中的所有mf1()都被隱藏

    void mf3(); //基類中的所有fm3()都被隱藏

    void mf4();

};
  • 現在有下面的呼叫程式碼:
Derived d;

int x;


d.mf1(); //正確

d.mf1(x); //錯誤,Base::fm1(int)被隱藏了


d.mf2(); //正確


d.mf3(); //正確

d.mf3(x); //錯誤,Base::mf3(double)被隱藏了

通過using宣告增加對基類成員函式的使用

  • 有時這種隱藏可能會違反基類與派生類之間的is-a關係(因為我們希望基類中有些行為在派生類中同樣可以使用)。因此我們可以使用using宣告表示式取消這種隱藏,在派生類中匯入基類的函式行為
  • 注意:使用using宣告時,當using在派生類的不同的訪問模式(public、protected、private)下,那麼基類的函式在派生類中就屬於該訪問模式
  • 演示案例:

class Base

{

private:

    int x;
    
public:

    virtual void mf1() = 0;

    virtual void mf1(int);

    virtual void mf2();

    void mf3();

    void mf3(double);

};


class Derived :public Base

{

public:

    using Base::mf1; //Base所有版本的mf1函式在派生類作用域都可見

    using Base::mf3; //Base所有版本的mf3函式在派生類作用域都可見


    virtual void mf1(); //重寫mf1()函式

    void mf3(); //隱藏了mf1(),但是mf3(double)沒有隱藏

    void mf4();

};
  • 現在有下面的呼叫程式碼:
Derived d;

int x;


d.mf1(); //正確,呼叫Derived::mf1()

d.mf1(x); //正確,呼叫Base::mf1(int)


d.mf2(); //正確,呼叫Derived::mf2()


d.mf3(); //正確,呼叫Derived::mf3()

d.mf3(x); //正確,呼叫Base::mf3(double)

使用轉交函式

  • 有時派生類會以private的方式繼承於基類,那麼基類中的所有內容對於派生類來說全都是不可見的
  • 即使是private,派生類也可以重寫(覆蓋)或隱藏基類的成員/函式。例如:
class Base

{

public:

    void mf1() {}

    void mf1(int) {}

};


class Derived :private Base

{

public:

    void mf1() {} //隱藏了基類的mf1()

};


int main()

{

    Derived d;

    int x;


    d.mf1(); //正確,使用Derived::mf1()

    d.mf1(x); //錯誤


    return 0;

}
  • 此時,我們也可以使用using宣告來將基類中的所有過載版本在派生類中都可見。例如:
class Base

{

public:

    void mf1() {}

    void mf1(int) {}

};


class Derived :private Base

{

public:

    using Base::mf1; //使Base中的所有mf1版本在派生類作用域中都可見

    void mf1() {} //隱藏Base::mf1()

};


int main()

{

    Derived d;
    
    int x;


    d.mf1(); //正確,使用Derived::mf1()

    d.mf1(x); //正確,使用Base::mf1(int)


    return 0;

}
  • 當然在有些時候,我們不希望基類中的所有過載版本在派生類中都可見,那麼可以自己設計一種轉交函式。例如:
class Base

{

public:

    virtual void mf1() = 0;

    virtual void mf1(int);

};


class Derived :private Base

{

public:

    //這是一個轉交函式

    virtual void mf1() {

        Base::mf1(); //呼叫基類的mf1()函式

    }

};


int main()

{

    Derived d;

    int x;


    d.mf1(); //正確,雖然呼叫的是Derived::mf1(),但是本質上呼叫的是Base::mf()

    d.mf1(x); //錯誤,Base::mf(double)被隱藏了


    return 0;

}

四、總結