C++GOW系列之(5):重载虚函数

Wesley13
• 阅读 600

原文请链接http://www.gotw.ca/gotw/005.htm

虚函数是非常基础的特性,如果你能回答上以下问题,你就能完全了解他们。

问题:

假设你正在浏览公司代码库的边角地带,你碰到了一段如下的代码。编写这段代码的人看上去像在实验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;
    }

答案:

首先是一些风格问题:

  • void main() ;

    这不是main函数的合法用法,尽管一些编译器允许这样使用。要使用"int main()"或者"int main(int, char*[])"。然而,要注意到,return语句在main中并不是必须的。如果你不写,编译器会为你自动加上"return 0;"。

  • delete pb;

     这看上去没什么错误,前提是基类的编写者使用了虚拟析构函数。事实上,通过基类指针删除一个没有虚析构函数的实例就像魔鬼一样,你最好期望它能立即奔溃。

        【规则】使基类的析构函数为虚

  •     Derived::f(complex)

     Derived 冰没有重载Base::f,而是隐藏了它。这非常重要,因为在Derived中,Base::f(int)和Base::f(double)在Derived中不可见。(注意,一些知名的编译器对此甚至连警告都没有)

        【规则】当提供了一个与继承而来的函数同名的函数时,确保使用"using"指示符将继承而来的函数引入类作用域中,如果你不想隐藏他们。

  • Derived::g(int i = 10)

     除非你想要愚弄大家一下,否则不要试图改变继承而来的默认参数值。(总的来讲,尽量使用继承而来的默认参数值并不是个坏主意,不过这是它本身的问题)在C++中这是合法的,而且是良好定义的,但是,请不要这样使用。通过下面的代码来展示它是如何迷惑人们的。

        【规则】永远不要修改继承而来的默认参数值。

现在我们清楚掉了主要的代码风格问题,让我们切回主题来看一下结果是否是编写者想要的。

 void main() {
        Base    b;
        Derived d;
        Base*   pb = new Derived;

        b.f(1.0);

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

d.f(1.0);

这调用了Derived::f(complex).为什么呢?回想一下,Derived并没有声明"using Base::f()"。显然,Base::f(int)和Base::f(double)并不会被调用。

这调用了Derived::f(complex),而编译器连警告都不会发出。这是因为"幸运的"在complex有从"double"的隐式转换,所以编译器把它视作"Derived::f(complex(1.0))"。

    隐式转换是因为在当前的草案中,complex的构造器并不是explicit。

pb->f(1.0);

这儿很有趣,它调用的是Base::f(double),尽管pb指向的是Derived类型。这是因为重载决议是在静态类型上完成的,而不是在动态类型上。

b.g();

这打印出了"10",这是因为它仅仅是调用了Base::g(int i = 10).没问题。

d.g();

这打印出了"20",这是因为它仅仅调用了Derived::g(int i = 20).依然没问题。

pb->g();

这打印出了"Derived::g() 10"。这会使你非常吃惊直到你意识到编译器这么做是多么的正确。这里要记住的是,像重载决议一样,默认参数来自于静态类型,而不是动态类型,因此是10.然而,这里恰巧是虚函数,因此调用的又是动态类型的方法。

    【译者观点】关于虚函数中的默认参数,在effective c++中,作者提倡的原则是不要在虚函数中使用默认参数,比此文中的规则更加严格,至于使用那种规则,就要见仁见智了。在译者本人的项目组中,看到的已有代码会使用虚函数的默认参数,毕竟省时省力。现代的代码补全工具会在派生类中根据基类声明自动添加同样的默认参数值。很多时候为了兼容客户代码,即使要重构,也必须继续使用默认参数。因此使用或者不使用并不是简简单单的一条规则,it depends。

如果你理解了最后几个段落(哎呀,原来是这样啊!),那你应该完全理解了这个主题,恭喜。

 delete pb;
    }

对于delete语句,无论如何它会崩溃,析构掉部分内存。详见上文关于虚析构函数部分。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Karen110 Karen110
3年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这