GotW #5: 覆盖虚函数

原文link:http://www.gotw.ca/gotw/005.htm
难度:6/10
虚函数太easy了?是吗?如果你能答出这道题,就算你牛。
问题:
你在公司代码库里乱翻,发现一段这样的代码,不知道谁写的,看起来像是在试验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()
虽然很多编译器都允许,但这是错的。用int main() 或者int main(int argc, char * argv[])。不过,并不需要显式的返回语句。当然返回错误代码是好的,不过即使没返回,编译器也会自动添上return 0的。
2.delete pb;
看起来没错,但只是看起来,除非你在基类定义了虚析构函数。delete一个指向基类的指针,如果没有虚析构函数,你就等着掉坑里吧。
[规则]:基类的析构函数定义为虚函数。而且要有函数体,即使是纯虚析构函数。
3.Derived::f(complex<double>)
Derived类不会重载基类的f(),而是隐藏它。这个差别很重要,因为这样基类的f(int)和f(double)在Derived类里是不可见的。而且要小心,很多编译器对这种情况是不报警的。
[规则]:在派生类里写了一个与基类函数名一样的函数时,如果你不想隐藏基类的那个函数,就用using语句把它包含进来。
4.Derived::g(int i = 10)
除非你确实想把别人搞晕,否则不要修改继承函数的默认参数。尽管这符合语法规定,而且也有良好的定义,但别这么干。至于后果请往下看。
[规则]:覆盖继承得来的函数时,永远不要修改默认参数值。
看这段代码会是什么结果。
    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”,所以Derived类不能调用Base::f(int)和Base::f(double)。可能本意是想调用Base::f(double),但是由于complex<double>有到double的隐式转换,结果编译器也不会报错。结果就是 Derived::f(complex<double>(1.0))。
pb->f(1.0);
尽管Base* pb指向了Derived类对象,但是这里调用的是Base::f(double)。因为重载是按静态类型(Base)判断的,而不是动态类型(Derived)。
b.g();
这句的输出是10。调用Base::g(int i = 10),没猫腻,很简单。
d.g();
这句的输出是"Derived::g() 20",也很简单的调用Derived::g(int i = 20)。
pb->g();
这句的输出是"Derived::g() 10"。搞不懂了吧。。你得搞清楚编译器对这句是怎么处理的。对于重载,默认参数是按静态类型(Base)取的,所以这里的默认参数就是10。但是,该函数是虚的,所以就要按照对象的动态类型(Derived)来调用。
当然,这个码农该拖出去tjjtds。
如果你把这几段话搞懂了,那就真的是懂了。恭喜!
delete pb;
}
这个delete,会破坏内存,部分内容被清理了。参照上面说过的虚析构函数。






posted on 2012-02-22 11:44 高兴 阅读(234) 评论(0)  编辑 收藏 引用 所属分类: GotW


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

统计

常用链接

留言簿

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜