• (转) C++中基类和派生类之间的同名函数的重载问题


      下面有关派生类与基类中存在同名函数 fn:

     1 class A  
     2 {  
     3 public:  
     4     void fn()  
     5     {}  
     6   
     7     void fn(int a)  
     8     {}  
     9 };  
    10   
    11 class B : public A  
    12 {  
    13 public:  
    14     void fn()   
    15     {}  
    16 };  
    17   
    18 int main()  
    19 {  
    20     B b;  
    21     b.fn(3);  
    22     return 0;  
    23 }    

    1、以上代码编译为什么不能通过? (问题在第21行,编译器会报怨说,B中,并不存在fn(int)的函数)。

    2、编译器这样做(即不允许通过这样的代码)的好处是什么?

      相信这是一个非常之普遍的问题了,在众多经典的C++书籍中,都会将之列为一个重要C++问题,详细地深入地讲解。我这里仅能简单回答,可能对有同样疑问的同学,有个快速了解的作用。由于出差在外,非常不方便,回答问题时既不能详细调试,也不能做必要的查经求典的动作(手头没书),犯错的地方,请大家指正,我会及时修订。

    回答如下:


      你涉及到一个C++中的重要的知识点。即:同名函数的重载动作,只发生在自由函数(即非成员),及同一个class/struct内部的函数之间。而不能跨越基类和派生类。当派生类写一个和基类同名(无论参数列表相同或不相同)的函数时,此时发生的动作叫“覆盖”。覆盖的意思,就是基类的同名函数,在派生类内,将变得无法直接调用(但可以间接调用)。

      首先,我们还是针对问题的本质,简化一下代码,抛弃无直接相关的枝节。

     1 struct A  
     2 {  
     3     void foo(int d)  
     4     {  
     5         cout << "A::foo - int" << endl;  
     6         cout << d << endl;  
     7     }  
     8 };  
     9   
    10 struct B : public A  
    11 {  
    12     void foo(double d) //覆盖了A::foo(int d);  
    13     {  
    14         cout << "B::foo - double" << endl;  
    15         cout << d << endl;  
    16     }  
    17 };  
    18   
    19 int main()  
    20 {     
    21     A a;  
    22     a.foo(10);  
    23      
    24     B b;  
    25     b.foo(10.2);  
    26     b.foo(2); //调用的仍然是B::foo,虽然2明显是个整数  
    27              
    28     return 0;  
    29 }  

    以上代码,运行之后输出结果大致如下:(注释为后加内容)

    A::foo - int
    10
    B::foo - double
    10.2
    B::foo - double //调用的仍然是B::foo,虽然2明显是个整数
    2

    那么,要如何才能调用基类的foo(int )呢?


    方法有两种,其一为“临时法”:

    B b;  
    b.A::foo(2); //显式调用A范围内的foo  

      其二就该叫“终身法”,哈哈这名字又是我瞎起的,更好的叫法,应叫“引狼入室法”,别掰了。回忆一下 namespace 的三种用法,其中一种称为“using declaration/使用声明”, 这里可以用上类似的代码(很多情况下,class/struct域,和一个namespace有相同的功能)。请看代码:

     1 struct B : public A  
     2 {  
     3     using A::foo; //通过“使用声明”,引入了A::foo……  
     4      
     5     void foo(double d)  
     6     {  
     7         cout << "B::foo - double" << endl;  
     8         cout << d << endl;  
     9     }  
    10 };  

      现在要调用时:

     1 int main(void)  
     2 {  
     3    B b;  
     4   
     5    b.A::foo(3); //调用的……当然是A::foo(int)  
     6    b.foo(2);    //调用的……也是A::foo(int)  
     7   
     8    b.foo(10.234); //调用的……B::foo(double)  
     9   
    10    return 0;  
    11 }  

      接下回答“编译器这样做的好处是什么?”


      这是为了避免“非恶意性的错误”。这也是C++语言设计中的一个重要原则:语法规则,会尽量让程序员避免“无意的错误”,但并不去管"有意,恶意,不怀好意的错误"。

      以A,B代码为例,想像一下,如果我是A的作者,你是B的作者。

      由于foo并不是“virtual/虚”函数,所以二者之间可能是各写各的。如果一开始我没有在A内写那个foo(int)函数,而你因为需要,在B中写了一个foo(double)函数,并且用起来很爽----因为此时基类中没有同名函数,所以你无论写 foo(2)也好,还是写foo(2.0)也好,由于int 到 double的转换是安全的,所以,两次都非常爽地调用了B自己的foo(double) 。通常这也是我们所要的。

      接着有一天,作为开发小组成员,我在修改A时,我觉得需要一个 void foo(int);于是我在A中加了这个函数。并且由于它不是virtual,最主要是: 由于我是基类的作者,我哪管得了天下到底有几个人派生了我的A类呢? 所以我才懒得告诉你A类中多了一个很普通的函数。但现在情况如何呢?如果按派生类和基类的普通同名函数也可以构成重载关系,完了完了,当你拿到A的新版,重新编译项目,一切正常,编译器不报任何错,可是你前面所说的那段代码却突然改为调用基类的那个,我刚刚写的同名函数,这还了得!

      通常,基类的作者,都是比较牛逼的人,为什么?因为他肩负着更多的责任。当一个类,以“基类”的形式提供出去以后,通常,它就不应该——有同学抢话说:“它就不应该再修改”——那倒不是(我们又不是在写COM组件),基类也要发展,也有版本升级,否则类库如何取得进步? 正确要求是:通常,它所做的改动,都应该是向前兼容的。即基类的修改,可以为派生类提供更多的新功能,但不应该影响了派生类原来已有的功能。在此要求下,如果C++的在本例中的规则是:写基类的人只是根据自己需要,写了一个普普通通的成员函数,结果就造成了派生类的原有行为被偷偷地修改,那这也太为难基类作者,他最终会每写一个函数,都使劲猜测会不会存在一个亚洲的,美洲的,南极大陆上的某个派生类的作者,在过去,或现在,或将来的时间写的某个函数正好同名,这太累了!基类的作者再牛,但你也不该把他逼到这份上啊!

      C++之父是英明的!OH Yeah~~~

      原文地址:基类和派生类之间的同名函数,存在重载吗?

      and more:c++(重载、覆盖、隐藏)

  • 相关阅读:
    动手学深度学习 | 使用和购买GPU | 15
    工作生活及未来畅想 —— 杂谈
    线段树 乌鸦喝水
    测试开发进阶——常用中间件概念——JNDI树理解(转载)
    测试开发进阶——常用中间件概念——JMX监听器理解
    测试开发进阶——常用中间件概念——JMS(Java消息服务)入门&基于Tomcat + JNDI + ActiveMQ实现JMS的点对点消息传送理解(转载)
    测试开发进阶——Servlet ——Servlet异常处理
    测试开发进阶——Servlet ——Servlet发送电子邮件
    测试开发进阶——Servlet ——Servlet上传文件到服务器
    测试开发进阶——Servlet ——Servlet通过JDBC进行数据库访问
  • 原文地址:https://www.cnblogs.com/Newdawn/p/4972687.html
Copyright © 2020-2023  润新知