天天看點

Effective C++ 條款33:避免遮掩繼承而來的名稱

考慮下面這段代碼:

int x; 
void someFunc()
{
    double x;    //local variable
    std::cin>>x; //read a new value to local x
}           

作用域圖如下:

Effective C++ 條款33:避免遮掩繼承而來的名稱

這個指涉的是local變量x,而不是global變量x,因為記憶體作用域會的名稱遮掩外圍作用域的名稱。當編譯器處于someFunc的作用域内并遭遇名稱x時,他在local作用域内查找是否有什麼東西帶着這個名稱。如果找到就不再找其他作用域。someFunc的x是double類型而global的x是int類型,并不要緊。C++的名稱遮掩規則(name-hiding rules)所做的唯一事情就是:遮掩名稱。至于名稱是否是相同或不同的類型,并不重要。

derived class繼承聲明于base class内的所有東西。derived class作用域被嵌套在base class作用域内:

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(); 
};           

假設derived class内的mf4的實作像這樣:

void Derived::mf4() { 
    mf2(); 
}           

作用域圖如下:

Effective C++ 條款33:避免遮掩繼承而來的名稱

當編譯器看到這裡使用名稱mf2,必須估算它指涉什麼東西。查找各作用域,看看有沒有某個名為mf2聲明式。首先查找local作用域(也就是mf4覆寫的作用域),沒找到任何東西名為mf2.于是查找其外圍作用域,class Derived覆寫的作用域。還是沒找到任何東西名為mf2.于是再往外圍移動,本例為base class。在那找到一個名為mf2的東西了,于是停止查找。如果Base還是沒有mf2,查找便繼續下去,首先找内含Base 的那個namespace的作用域,最後往global作用域找去。

這次我們重載mf1和mf3,并添加一個新版mf3到derived去:

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(); 
    void mf3(); 
    void mf4(); 
};           

作用域圖:

Effective C++ 條款33:避免遮掩繼承而來的名稱

base class内所有名為mf1和mf3的函數都被derived class内的mf1和mf3函數遮掩掉了!

Derived d; 
int x; 
d.mf1(); 
d.mf1(x);//錯誤,Derived::mf1遮掩了Base::mf1 
d.mf2(); 
d.mf3(); 
d.mf3(x);//錯誤,Derived::mf3遮掩了Base::mf3           

即使base class和derived classes内的函數有不同的參數類型也适用,而且不論函數是virtual或non-virtual也适用。

解決上面的問題的方法!

可以用using聲明式達成目标:

class Derived : public Base { 
public:
//base class内的public名稱在publicly derived class内也應該是public。 
    using Base::mf1;    // 讓base class内為mf1和mf3的所有東西 
    using Base::mf3;    //在Derived class作用域内都可見(并且public) 
    virtual void mf1(); 
    void mf3(); 
    void mf4(); 
};

Derived d; 
int x; 
d.mf1(); 
d.mf1(x);//現在沒問題了,調用Base::mf1 
d.mf2(); 
d.mf3(); 
d.mf3(x);//現在沒問題了,調用Base::mf3           

作用域圖:

Effective C++ 條款33:避免遮掩繼承而來的名稱

這意味着如果你繼承base class并加上重載函數,而你又希望重新定義或覆寫(推翻)其中一部分,那麼你必須為那些原本會被覆寫的每一個名稱引入一個using聲明式,否則某些你希望繼承的名稱會被覆寫。

有時候我們并不希望繼承base class的所有函數,這個時候就不能用public方式繼承了,因為這違反了”base與derived classes之間的is-a關系!是以這個時候就要用private的繼承方式了!

如果Derived唯一想繼承的是base class中的那個無參數版本的mf1。using在這裡派不上用場,using會令繼承而來的某給定名稱之所有同名函數在Derived class中都可見(在這裡如果用using的話,那麼在base class中的所有mf1在derived class中都可見!)。是以在這種情況下,我們需要一個簡單的轉交函數(forwarding function):

class Base { 
private: 
    int x; 
public: 
    virtual void mf1() = 0; 
    virtual void mf1(int); 
    virtual void mf2(); 
    void mf3(); 
    void mf3(double); 
};

class Derived : private Base { 
public: 
    virtual void mf1()//轉交函數 
    { 
        Base::mf1();//暗自轉成inline 
    }
};

Derived d; 
int x; 
d.mf1();//調用的是Derived::mf1 
d.mf1(x);//錯誤,Base::mf1()被遮掩了           

總結

  1. derived class内的名稱會遮掩base classes内的名稱。在public繼承下沒有人希望如此。
  2. 為了讓遮掩的名稱再見天日,可使用using聲明式或轉交函數(forwarding functions)。

繼續閱讀