C++ 避免隐藏继承而来的名称

作者:fuliangcheng 和c/c++相关  
    关于C++中继承这个概念相比大家都很熟悉,那么子类究竟能从父类继承到哪些东西,哪些东西又是子类继承不到的呢?可能很多人都会觉得父类所有的东西都会被子类继承,包括成员函数和成员变量,否则就违背了“父子关系”这字面上的意思,其实不然,首先我们先看一小段简单的代码,这段代码很容易理解

点击(此处)折叠或打开

  1. #include <iostream>

  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6.     virtual void fun1() = 0;
  7.     virtual void fun1(int)
  8.         {

  9.         }
  10.     virtual void fun2()
  11.         {

  12.         }
  13.     void fun2(int)
  14.         {

  15.         }

  16.     void fun3(float)
  17.         {

  18.         }
  19. };

  20. class Derived:public Base
  21. {
  22. public:
  23.     virtual void fun1()
  24.         {

  25.         }
  26.     void fun3()
  27.         {

  28.         }
  29.     void fun4()
  30.         {

  31.         }

  32. };


  33. int main()
  34. {
  35.     Derived a;
  36.     a.fun1(); //correct Derived:fun1();
  37.     //a.fun1(8); //incorrect
  38.     a.fun2(); //correct Base:fun2()
  39.     a.fun2(3); //correct Base:fun2(int);
  40.     a.fun3(); //correct Derived:fun3()
  41.     //a.fun3(3.0); //incorrect
  42.     cout << "Hello world!" << endl;
  43.     return 0;
  44. }
仔细看主函数里面的注释,这段代码几乎会让很多人第一次看到都很诧异

首先普及两点常识,大虾略过
1.  重载这个概念很重要,这是C++比C多出来的功能,我们知道C是不支持函数重载的,这也是限制C开发大型程序的原因之一。
    重载发生在同一个类中(父类和子类不叫同一类,虽然可以称他们为同一类型)
    函数名称相同,参数不同
    函数重载忽略返回值和virtual关键字(如果仅仅是返回值不同或有无virtual关键字的区别,不算函数重载)
下面是对函数重载为什么忽略返回值的解释:
    void Function(void);
    int Function (void);
    上述两个函数,第一个没有返回值,第二个的返回值是int 类型。如果这样调用函数:
    int x = Function ();
   则可以判断出Function 是第二个函数。问题是在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。
     所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符
2.  隐藏这个概念
    隐藏就是“内部作用域”的名称隐藏"外部作用域“的名称,例如局部变量会隐藏同名的全局变量,同样, 子类的名称会隐藏父类的名称。这篇文章主要解释的就是关于继承中存在的隐藏的问题

好了,有了上面的知识,我们就可以对上面的代码加以分析了。Base类中的fun1和fun2函数都被重载了,我们在主函数中调用fun1()函数是没有问题的,因为在Derived类中存在这个函数,这里我们调用的fun1()就是Derived类中的fun1()。再往后面看,我们调用fun1(8)函数会出现错误,在Base类中明明存在virtual void fun1(int)这个函数,我们又使用了public继承,为什么这里调用会出错呢?难道是没有继承到fun1(int)这个函数,是不是很奇怪?这是因为子类中存在fun1()函数,编译器在子类中找到fun1()这个符号,就不会再去父类里查找fun1(int),虽然父类里面有我们需要的函数。但是编译器不管,只要找到fun1()这个符号,它就再往下查找。这就导致了父类中所有与子类同名的函数都将被隐藏,而这肯定与我们绝大多数时候的意愿是相违背的。

C++这么做是有原因的,记住一句话,C++每一种行为的背后都有其深层的原理,搞清楚这背后的原理对我们理解C++很有帮助。
关于这种行为背后原理的权威解释,在《Effetive C++》中有这么一段话:
   ”这些行为背后的原理是为了防止你在程序库或应用框架内建立新的derived class时从疏远的的base classes继承重载函数“。
仔细分析下这句话C++这么做确实有道理的,如果程序规模比较大,类继承层次比较深时,如果C++没有这种隐藏特性,那么子类中的重载函数数量就会非常多,这样就增加了我们调用这些函数出错的可能性。但是很多情况下我们不需要编译器这样”弄巧成拙“,那么如何解决这个问题,也就是让编译器不要隐藏域子类同名的函数。显然C++是给出了解决办法的,我们可以使用using声明来解决这个问题。看下面的代码

点击(此处)折叠或打开

  1. #include <iostream>

  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6.     virtual void fun1() = 0;
  7.     virtual void fun1(int)
  8.         {

  9.         }
  10.     virtual void fun2()
  11.         {

  12.         }
  13.     void fun2(int)
  14.         {

  15.         }

  16.     void fun3(float)
  17.         {

  18.         }
  19. };

  20. class Derived:public Base
  21. {
  22. public:
  23.     using Base::fun1;
  24.     using Base::fun2;
  25.     using Base::fun3;
  26.     virtual void fun1()
  27.         {

  28.         }
  29.     void fun3()
  30.         {

  31.         }
  32.     void fun4()
  33.         {

  34.         }

  35. };


  36. int main()
  37. {
  38.     Derived a;
  39.     a.fun1(); //correct Derived:fun1();
  40.     a.fun1(8);
  41.     a.fun2(); //correct Base:fun2()
  42.     a.fun2(3); //correct Base:fun2(int);
  43.     a.fun3(); //correct Derived:fun3()
  44.     a.fun3(3.0);
  45.     cout << "Hello world!" << endl;
  46.     return 0;
  47. }
通过在Derived类中引入using申明,我们将Base类中的所有fun1,fun2, fun3函数添加进了子类,这样子类对象在调用这些函数时就没有任何问题了。
所以当我们继承父类,而且父类存在重载函数,我们又在子类重新定义了这些重载函数的一部分,那么我们必须为那些原本会被隐藏的名称加上using申明,否则那些我们希望继承的名称会被隐藏掉而又不给我们任何提示。

现在让我们再深入一点,如果我们不希望继承所有父类中的成员函数(这里与今天的主题并不矛盾),这在public继承中是不可能的,也是没有意义的。在public继承中父类和子类关系式is-a。然后这在private继承中是有意义的,这么说吧,假设我们只想继承Base类中带参数的fun2版本,即void fun2(int)这个函数。上面提到的using声明显然已经不足以解决这个问题,因为using声明会将父类中所有指定
名称的函数都在子类中可见,所以需要另外的解决办法,看代码:


点击(此处)折叠或打开

  1. #include <iostream>

  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6.     virtual void fun1() = 0;
  7.     virtual void fun1(int)
  8.         {

  9.         }
  10.     virtual void fun2()
  11.         {

  12.         }
  13.     void fun2(int)
  14.         {

  15.         }

  16.     void fun3(float)
  17.         {

  18.         }
  19. };

  20. class Derived:private Base
  21. {
  22. public:
  23.     virtual void fun1()
  24.         {
  25.             cout<<"This function is implemented to avoid to be a abstract class";
  26.         }
  27.     void CallBaseFun2(int x)
  28.         {
  29.             Base::fun2(x);
  30.         }


  31. };

  32. int main()
  33. {
  34.     Derived a;
  35.     CallBaseFun2(3);
  36.     //a.fun2(); //incorrect

  37.     cout << "Hello world!" << endl;
  38.     return 0;
  39. }
在这段代码中,将Base类中的fun2(int)函数放在Derived类中的CallBaseFun2(int x)函数中实现调用,而Base中的无参数fun2版本void fun2()不能调用。好了,关于C++中继承和隐藏的类容就差不多了。

总结:
Derived类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过引入using声明,在private继承中我们可以通过一个转接函数来解决这个问题。


参考文献:
1. 《Effective C++》

相关资料:

C++ 避免隐藏继承而来的名称来源网络,如有侵权请告知,即处理!

编程Tags: