C/C++编程:避免隐藏继承而来的名称

亦凉 2023-01-20 05:00 324阅读 0赞

名称的隐藏

这与作用域有关

  1. int x;
  2. void someFunc(){
  3. double x;
  4. std::cin >> x; // 读一个新值赋予local变量x
  5. }

C++名称查找规则:先去local作用域内查找,如果找到就不再查找其他作用域
在这里插入图片描述

继承中的名称隐藏

现在导入继承。我们知道,当位于一个派生类成员函数内refer to 基类内的某物(也许是个成员函数、typedef、或者成员变量)时,编译器可以找出我们所refer to的东西,因为派生类继承了声明与基类的所有东西。实际上,派生类作用域被嵌套在基类作用域内,像这样:

  1. class Base{
  2. private:
  3. int x;
  4. public:
  5. virtual void mf1() = 0;
  6. virtual void mf2();
  7. void mf3();
  8. };
  9. class Derived : public Base{
  10. public:
  11. virtual void mf1();
  12. voud mf4();
  13. };

在这里插入图片描述
假设派生类的mf4的实现部分是这样的:

  1. void Derived::mf4(){
  2. ...
  3. mf2();
  4. ...
  5. }

当编译期看到这里使用名称mf2,必须估算它refer to什么东西。编译器的做法是查找各作用域,看看有没有某个名为mf2的声明式。首先找到local作用域(也就是mf4覆盖的作用域),在那里没有找到任何东西名为mf2。于是查找外围作用域,也就是class Derived覆盖的作用域,还是没有找到。于是再往外围移动(此时为Base class),在这里编译器找到了一个名为mf2的东西了,于是停止查找。如果Base内还是没有找到,就会继承去找内含Base的那个作用域,最后往global作用域。

我们再来看一个例子:

  1. class Base{
  2. private:
  3. int x;
  4. public:
  5. virtual void mf1() = 0;
  6. virtual void mf1(int);
  7. virtual void mf2();
  8. void mf3();
  9. void mf3(double);
  10. };
  11. class Derived : public Base{
  12. public:
  13. virtual void mf1();
  14. void mf3();
  15. void mf4();
  16. };

在这里插入图片描述

上面例子中,由于以作用域为基础的”名称查找规则”,因此基类所有名为mf1和mf3的函数都被派生类的mf1和mf3函数都遮掩掉了。从名称查找的观点来看:Base::mf1()和Base::mf3不再被派生类继承:

  1. Derived d;
  2. int x;
  3. d.mf1(); // ok, 调用Derived::mf1;
  4. d.mf1(x); // error,因为Derived::mf1遮掩了Base::mf1
  5. d.mf2(); // ok,调用Base::mf2
  6. d.mf3(); // ok, 调用Derived::mf3
  7. d.mf3(x); // error,因为Derived::mf3遮掩了Base::mf3

这可以看出,即使基类和派生类的函数有不同的参数类型、不管函数是virtual还是non-virtual,都有名称隐藏问题

这些行为背后的基本理由是为了防止你在程序库或者应用框架内建立新的派生类时附带的从疏远的基类继承重载函数。不幸的是你通常会想继承重载,实际上如果你正在使用public继承而又不继承那些重载函数,就会违反基类和派生类的is-a关系,因此你会想要推翻C++对”继承而来的名称“的缺省隐藏问题。

你可以使用using声明达成目标:

  1. class Base{
  2. private:
  3. int x;
  4. public:
  5. virtual void mf1() = 0;
  6. virtual void mf1(int);
  7. virtual void mf2();
  8. void mf3();
  9. void mf3(double);
  10. };
  11. class Derived : public Base{
  12. public:
  13. using Base::mf1;
  14. using Base::mf3; // 让基类的mf1和mf3内的所有东西在派生类的作用于可见
  15. virtual void mf1();
  16. void mf3();
  17. void mf4();
  18. };

现在,继承机制恢复了:

  1. Derived d;
  2. int x;
  3. d.mf1(); // ok, 调用Derived::mf1;
  4. d.mf1(x); // ok, 调用Base::mf1
  5. d.mf2(); // ok,调用Base::mf2
  6. d.mf3(); // ok, 调用Derived::mf3
  7. d.mf3(x); //ok, 调用Base::mf3

有时候你并不想继承基类的所有函数,这是可以理解的。

  • 在public继承下,这绝对不可能发生,因为它违反了public继承所暗示的”基类和派生类之间的is-a关系”(这也是上面using声明式被放在派生类的public区域的原因:基类的public名称在publicly derived class内也应该是public)
  • 在private继承下,这是有意义的。假设Derived以private方式继承Base,而Derived唯一想继承的mf1是那个无参数版本。using声明式在这里就没有用处,因为using声明会令继承而来的某给定名称的所有同名函数在派生类中都可见。
  • 因此,public/private继承不能实现只继承某些函数,解决方法:使用一个简单的转发函数:

    class Base{

    private:

    1. int x;

    public:

    1. virtual void mf1() = 0;
    2. virtual void mf1(int);
    3. virtual void mf2();
    4. void mf3();
    5. void mf3(double);

    };

    class Derived : private Base{

    public:

    1. virtual void mf1(){
    2. // 转发函数
    3. Base::mf1(); // 暗自称为inline
    4. }

    };


    Derived d;
    int x;
    d.mf1(); // ok, 调用的是Derived::mf1
    d.mf2(x); // errro, Base::mf1(int)被隐藏了

总结

  • 派生类的名称会隐藏基类中的名称,在public继承下从来没有人希望如此
  • 为了让被遮掩的名称重见天日,可以使用using声明式或者转发函数

发表评论

表情:
评论列表 (有 0 条评论,324人围观)

还没有评论,来说两句吧...

相关阅读