Effective C++条款33:继承与面向对象之(避免遮掩继承而来的名字)

一、变量隐藏使用规则

  • 当全局和局部存在相同的变量时,在局部作用域中,全局作用域的变量名会被隐藏,优先使用局部的变量
  • 例如:
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;
}

四、总结

发布了1504 篇原创文章 · 获赞 1063 · 访问量 43万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104760199