Java、C++中子类对父类函数覆盖的可访问性缩小的区别介绍
前言
“Java和C++中子类对父类函数覆盖的可访问性缩小的问题”的题目看起来比较学术化,但的确是一个容易忽视的问题。本文力求详细阐述这一问题在Java以及C++中的区别。
先介绍什么是“子类对父类函数覆盖的可访问性缩小”。对于继承而言,子类可以覆盖父类的“虚函数”——尽管Java中没有虚函数这一术语,但可以把Java的所有函数都看作虚函数,因为Java的所有函数都可以被子类覆盖。这里仅借用“虚函数”这一名词的含义,不深究语言的细节。Java和C++都允许在覆盖时,改变函数的可访问性。所谓“可访问性”,就是使用public、protected、private等访问控制符进行修饰,用来控制函数能否被访问到。通常可访问性的顺序为(由于C++中没有包的概念,因此暂不考虑包访问控制符,这并不影响这里的讨论):
public>protected>private
以Java为例:
classBase{ protectedvoidsayHello(){ System.out.println("HelloinBase"); } } classChildextendsBase{ publicvoidsayHello(){ System.out.println("HelloinChild"); } }
注意:这里的sayHello()函数。父类Base中,该函数使用protected访问控制符进行修饰。而子类将其改用public,这不会有任何问题。子类对父类函数覆盖时,扩大可访问性,通常都不是问题。
当子类对父类函数覆盖的可访问性缩小时,Java和C++采取了不同的策略。
首先以Java为例,看下面的代码:
classBase{ publicvoidsayHello(){ System.out.println("HelloinBase"); } } classChildextendsBase{ privatevoidsayHello(){ System.out.println("HelloinChild"); } }
上面的代码中,高亮的第8行会有编译错误——这段代码根本不能通过编译!Java不允许子类在覆盖父类函数时,缩小可访问性。至于原因,我们可以用一个例子来说明。例如我们在类外部写下面的代码:
Basebase=newBase(); base.sayHello(); base=newChild(); base.sayHello();
假如之前的代码可以通过编译,那么就存在这么一种可能:当base指向newBase()时,sayHello()是可以访问到的,但是当base指向newChild()时,sayHello()却无法访问到!在Java看来这是一个矛盾,应该避免出现这种问题,因此,Java从编译器的角度规定我们不能写出上面的代码。
针对C++,情况又有所区别。来看C++的例子:
classBase{ public: virtualvoidsayHello(){ std::cout<<"HelloinBase"; } } classChild:publicBase{ private: voidsayHello(){ std::cout<<"HelloinChild"; } }
这段代码在C++中是完全正确的。注意,这里的子类在覆盖父类函数时,缩小了可访问性。如果你没有看出有什么问题,那么我们完全可以在类外部写出下面的代码:
Childchild; child.sayHello();//不能通过编译,因为sayHello()是private的 static_cast(child).sayHello();//可以通过编译,因为sayHello()是public的
第2行调用是失败的,因为在Child中,sayHello()是private的,不能在外部调用。然而,当我们使用static_cast将Child强制转换成Base对象时,事情发生了改变——对于Base而言,sayHello()是public的,因此可以正常调用。
针对这一点,C++标准的Memberaccesscontrol一章中的Accesstovirtualfunctions一节可以找到如下的例子:
classB{ public: virtualintf(); }; classD:publicB{ private: intf(); }; voidf(){ Dd; B*pb=&d; D*pd=&d; pb->f();//OK:B::f()ispublic,D::f()isinvoked pd->f();//error:D::f()isprivate }
对此,C++标准给出的解释是:
Accessischeckedatthecallpointusingthetypeoftheexpressionusedtodenotetheobjectforwhichthememberfunctioniscalled(B*intheexampleabove).Theaccessofthememberfunctionintheclassinwhichitwasdefined(Dintheexampleabove)isingeneralnotknown.
简单翻译过来有两条要点:
- 访问控制是在调用时检查的,也就是说,谁调用了这个函数,就检查谁能不能访问这个函数
- 类中成员函数的可访问性一般而言是不知道的,也就是说,检查可访问性时,并不能知道这个函数在定义时到底是public的还是private的,因此也就无法据此检查可访问性
正因如此,C++的调用方似乎可以通过一些技巧性转换,“巧妙地”调用到原本无法访问的函数。一个更加实际的例子是:Qt里面,QObject::event()函数是public,而其子类QWidget的event()函数则改变成protected。具体可以阅读Qt的相关代码。
总结来说,在子类覆盖父类函数时,Java严格限制了子类不能缩小函数可访问性,但C++无此限制。个人认为,从软件工程的角度来说,Java的规定无疑更具有工程上面的意义,函数的调用也更加一致。C++的标准则会明显简化编译器实现,但是对工程而言并不算很好的参考。
PS:C++标准的正式版是需要购买的,但是草案可以免费下载。C++标准草案的下载地址可以在下面的页面找到:https://isocpp.org/std/the-standard
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。