Guru of the Week 条款05:覆写虚拟函数

原创 2001年10月23日 20:12:00
 

GotW #05 Overriding Virtual Functions

著者:Herb Sutter     

翻译:kingofark

[声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。

Revision 1.0

Guru of the Week 条款05:覆写虚拟函数<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

难度:6 / 10

 

(虚拟函数(virtual function)真是一个招人喜欢的基本特性,对吗?好吧,如果你能回答下面这个问题,你就会发现她们有时真是冷若冰霜、寒气刺骨。)

 

 

[问题]

 

当你在一个布满灰尘的角落翻寻公司的存档代码时,你偶然中发现了无名氏编写的一段如下的程序。这位无名氏程序员好像曾经试图用这个程序做试验,看看某些C++特性的运作状况。你知道这个程序员希望程序运行后打印什么结果吗?你知道程序运行后的实际结果吗?

 

    #include <iostream>

    #include <complex>

    using namespace std;

 

    class Base {

    public:

        virtual void f( int ) {

            cout << "Base::f(int)" << endl;

        }

 

        virtual void f( double ) {

            cout << "Base::f(double)" << endl;

        }

 

        virtual void g( int i = 10 ) {

            cout << i << endl;

        }

    };

 

    class Derived: public Base {

    public:

        void f( complex<double> ) {

            cout << "Derived::f(complex)" << endl;

        }

 

        void g( int i = 20 ) {

            cout << "Derived::g() " << i << endl;

        }

    };

 

    void main() {

        Base    b;

        Derived d;

        Base*   pb = new Derived;

 

        b.f(1.0);

        d.f(1.0);

        pb->f(1.0);

 

        b.g();

        d.g();

        pb->g();

 

        delete pb;

    }

 

 

[解答]

 

首先,我们谈谈编码风格方面的问题:

 

1. void main()

 

这并不是一个合法的main声明,尽管许多编译器都允许这种写法。应该使用“int main()”或者“int main(int argc, char* argv[])”。

 

然而,其实你仍然不需要添加任何返回语句(虽然有时加上返回语句是为了形成一种向外部调用者报告错误的良好编码风格)。实际上,如果main没有返回语句,其效果等同于执行了“return 0;”。

 

2. delete pb;

 

这看起来好像既无公害又无伤大雅——当然,前提是Base的编写者提供了一个虚拟析构函数(virtual destructor)。然而,就像我们在这个程序中所看到的那样,在没有虚拟析构函数(virtual destructor)的情况下通过指向基类的指针进行删除操作,这简直就是邪恶的犯罪,同时也是幼稚和简单的——如此以来,崩溃就是你所能期待的最好的事情了。

 

[规则]:把基类的析构函数(virtual destructor)声明为virtual

 

3. Derived::f(complex<double>)

 

Derived并没有重载(overloadBase::f,而是隐藏了它。这个细节非常重要,因为这意味着在Derived中,Base::f(int)Base::f(double)是不可见的!(更何况某些流行的编译器对此种情况甚至都不给出一个警告信息。)

 

[规则]:当派生类中的函数与基类中的函数同名,而你又不想在派生类中隐藏这些基类函数的时候,请使用using声明来把它们定置到可用范围(scope)之内。

 

4. Derived::g(int i = 10)

 

除非你是故意要把别人搞糊涂,否则不要改变你所覆写(override)的继承函数(inherited function)的缺省参数。(一般来说,进行覆写而不使用参数缺省值并不是一个坏主意,但那是其本身的问题。)是的,这是一个合法合理的C++语句;不错,其结果也被很好的定义了;但是,不,请不要这样做。往下接着看,你会发现它到底是如何把人搞糊涂的。

 

[规则]:绝不要改变覆写的继承函数(overridden inherited function

 

 

好了,让我们不要再谈那些编码风格的琐事了,现在我们就来看看主程序到底是不是按照那位无名氏程序员所期望的方式运作的。

 

    void main() {

        Base    b;

        Derived d;

        Base*   pb = new Derived;

 

*        b.f(1.0);

 

没问题,调用Base::f(double)

 

*        d.f(1.0);

 

这条语句调用Derived::f(complex<double>)。为什么?如前所述,Derived中没有声明“using Base::f;”,所以Base::f(int)Base::f(double)都不能被调用。

 

无名氏先生也许原本是想要调用Base::f(double),但是此处他连一个编译错误信息也得不到,因为幸运的是(?),complex<double>包含有一个从double的隐式转换(*),因而编译器把它看成是Derived::f(complex<double>(1.0))

 

*)注:在现有的C++标准草案中,转换构造函数(conversion constructor)不是显式的(explicit)。

 

*       pb->f(1.0);

 

有趣的事情在这里发生了:虽然Base* pb指向一个Derived对象,但这个语句还是调用Base::f(double),因为重载解析过程(overload resolution)会以静态类型(在这里是指Base)完成,而不是以动态类型(在这里是指Derived)完成。

 

*        b.g();

 

这条语句只是简单的打印“10”,因为它引起对Base::g(int)的调用,其函数参数缺省值为10。这没什么好奇怪的。

 

*        d.g();

 

这条语句打印“Derived::g()20”,因为它引起对Derived::g(int)的调用,其函数参数缺省值为20。这也没什么好奇怪的。

 

*        pb->g();

 

这条语句打印“Derived::g()10”。这个结果也许会暂时死锁你的精神刹车,导致你声嘶力竭的进入一种精神停滞状态——直到你认识到,编译器的所作所为是再正常不过的了(虽然我们可怜的无名氏毫无疑问的应该被枪毙)。要记住,确定缺省参数和重载一样,是以对象的静态类型(在这里是指Base)完成的,因此选用了缺省值10。然而,函数又恰好是virtual的,所以实际被调用的函数取决于对象的动态类型(在这里是指Derived)。

 

最后,如果你能理解这剩下的几个语句(尽管你会因此说:“噢呕!”),那么你就终于可以理解我开头所说的“冷若冰霜、寒气刺骨”的意思了。祝贺你!

 

*       delete pb;

    }

 

删除操作,当然,会留下一些只被部分清除的东西,使内存变得不可捉摸……好好看看前面关于虚拟析构函数(virtual destructor)的叙述吧。

《Effective C++ 》学习笔记——条款05

《Effective C++ 》学习笔记——条款05 Know what functions C++ silently writes and calls
  • lx417147512
  • lx417147512
  • 2014年11月03日 21:38
  • 736

mysql的week函数与JAVA计算周的差别问题

1、问题: 在某些情况下,会需要将日期按周来进行排序或统计,mysql就要用到week()或yearWeek()函数,就会发现,比如2016年的某一天,在mysql里面是属于第30周,但在JAVA中使...
  • cwfreebird
  • cwfreebird
  • 2017年01月22日 15:06
  • 1215

【C++11】final, override,重载,重写覆盖,重写隐藏

1:final, override的作用1:final的作用:修饰类或成员函数修饰类时: 表示本类禁止被继承; 修饰成员函数: virtual成员函数:表示不允许子类重写覆盖,但可以重写隐藏 ...
  • u013777351
  • u013777351
  • 2015年09月09日 14:53
  • 836

Java 覆写(Override)详解

一、覆写概念 既然现在出现了继承的关系,那么就存在了子类和父类的联系,而在子类之中有可能定义和父类完全相同的方法或属性的名称,这个时候就称为覆写了。 二、方法的覆写 当子类定义了和父类在方法名称...
  • wei_zhi
  • wei_zhi
  • 2016年10月11日 13:13
  • 1994

覆写JAVA中的CompareTo()方法与toString()方法的实现

package org.lza; import java.util.Arrays; public class ComparableDemo { public static void main(S...
  • LZA407
  • LZA407
  • 2014年03月20日 20:24
  • 779

Java基础回顾:覆写equals()方法

覆写equals()方法需要注意的问题: ├ . 不能与一个null值进行比较,否则会报NullPointerException异常 ├ . 类型不同时,不能进行比较,否则会报ClassCastE...
  • sinat_18882775
  • sinat_18882775
  • 2016年05月23日 15:36
  • 879

覆写equals方法必须覆写hashCode方法

覆写equals方法必须覆写hashCode方法,这条规则基本上每个Javaer都知道,这也是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?本建议就来解释该问题,我们先...
  • lexang1
  • lexang1
  • 2015年10月29日 22:58
  • 766

如何覆写java中的equals和hashcode方法

Equals 和 hashCode是java中一个对象的两个基本方法和core java的重要组成部分。Equals用来比较对象的相等性,hashcode用来生成相应对象的整数形编码。Equals ...
  • SKY__LAND
  • SKY__LAND
  • 2013年11月21日 12:14
  • 2348

覆写equals方法

我们在写java类时,经常会覆写equals()方法,目的是根据业务判断两个对象是否相等。比如...
  • u013038643
  • u013038643
  • 2016年07月31日 21:41
  • 567

hashCode和equals什么时候覆写以及怎样覆写

1.何时需要重写equals方法? 当一个类具备自己特有的“逻辑相等”的概念。 2.如何覆写equals方法 (1)使用instanceof 操作符检查实参是否为正确的类型 如果传进来的类型不...
  • seek24
  • seek24
  • 2013年10月29日 14:40
  • 724
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Guru of the Week 条款05:覆写虚拟函数
举报原因:
原因补充:

(最多只允许输入30个字)