Effective C++读书笔记:条款33

Aki 发布于 2022-10-03 243 次阅读


33避免遮掩继承而来的名称:

C++的名称遮掩规则

名称遮掩规则做的事就是:遮掩名称。

  • 假如这个名称是一个变量,那么无论这两个变量的类型是否相同,都会被遮掩。
  • 假如这个名称是一个函数,那么无论函数的参数有几个,无论这个同名的函数有几个重载的版本,这些函数全都会被遮掩。

派生类的成员函数查找名称的顺序:

  • 先在成员函数体内查找(local作用域)。
  • 找不到的情况下,在派生类内查找。
  • 找不到的情况下,在派生类所继承的基类内查找。
  • 找不到的情况下,在派生类所属namespace查找。
  • 找不到的情况下,在global作用域查找。
  • 找不到的情况下,则会报错。

具体问题:

这章讨论的是,在继承的情况下,父类子类同名函数的隐藏问题。考虑下面这段代码:


class Base {
private:
    int x = 0;
public:
    virtual void mf1() = 0;      //纯虚函数
    virtual void mf1(int x){}    //虚函数
    virtual void mf2(){}         //虚函数
    void mf3(){}                 //普通函数
    void mf3(double x){}         //普通函数
    
};

class Derived : public Base {
public:
    void mf1() override {}  //重写了纯虚函数
    void mf3(){}            //普通函数
    void mf4(){}            //普通函数
   
};

在上面这段代码当中,base class当中所有的mf1和mf3的函数都被derived class内的mf1以及mf3函数遮掩掉了。Base::mf1和Base::mf3不会被Derived继承:(参考下面这段调用)

Derived d;
int x(0);
...
d.mf1(); // 正确,调用Derived::mf1()
d.mf1(x); // 错误,因为Derived::mf1()遮掩了Base::mf1(int x)
d.mf2(); // 正确,调用了Base::mf2()
d.mf3(); // 正确,调用了Derived::mf3()
d.mf3(x); // 错误,Derived::mf3()遮掩了Base::mf3(double x)

如果我们想要避免这样的遮掩呢,换句话说,调用父类的方法,这时候可以使用using:

class Derived : public Base {
public:
    void mf1() override {}  //重写了纯虚函数
    void mf3(){}            //普通函数
    void mf4(){}            //普通函数
    using Base::mf1;
    using Base::mf3;      // 让Base class内名为mf1和mf3的所有东西在Derived作用域内都可见
   
};

现在我们进行调用:

Derived d;
int x(0);
d.mf1(); // 正确,调用Derived::mf1()
d.mf1(x); // 正确,调用Base::mf1(int x)
d.mf2(); // 正确,调用了Base::mf2()
d.mf3(); // 正确,调用了Derived::mf3()
d.mf3(x); // 正确,调用Base::mf3(int x)

现在更进一步,我们知道在Base类当中有两个mf3函数,分别拥有不同的参数,如果我们想让部分被遮掩呢?(这样更加灵活),这个时候,using可能排不上用场,我们需要一个新的用法:

class Base {
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        ... // 与前面相同
};
class Derived: private Base {
    public:
        virtual void mf1() // 转交函数
        { Base::mf1(); } 

...
Derived d;
int x(0);
d.mf1(); // 正确,调用了Derived::mf1(),同时函数内部调用了Base::mf1()
d.mf1(x); // 错误

这样的技法我们称之为转交函数(forwarding function)

请记住:

  • derived class内的名称会遮掩base classes内的名称,在public继承下从来没有人会希望如此。
  • 为了让被遮掩的名称重见天日,可以使用using声明式或者转交函数(forwarding functions)。