美文网首页C++程序员技术文
Item 37:绝不重新定义继承而来的缺省参数值

Item 37:绝不重新定义继承而来的缺省参数值

作者: 0d339158f1a6 | 来源:发表于2016-04-08 09:48 被阅读105次
    向上

    Item 37:绝不重新定义继承而来的缺省参数值

    Item 37:Never redefine a function's inherited default parameter value

    本条款的讨论局限于:继承一个带有缺省参数值的virtual函数

    本条款成立的理由是:virtual函数是动态绑定(dynamically bound),而缺省参数却是静态绑定(statically bound)。

    静态绑定又名前期绑定,early binding;动态绑定又名后期绑定,late binding。

    1. 静态类型和动态类型

    对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型。
    对象的所谓动态类型(dynamic type)是指"目前所指对象的类型"。也就是说,动态类型可以表现出一个对象将会有什么行为。

    举个例子:

    class Shape {
    public:
        enum ShapeColor {Red, Green, Blue};
        //所有形状都必须提供一个函数,用来会出自己
        virtual void draw(ShapeColor color = Red) const = 0;
        ...
    };
    
    class Rectangle: public Shape{
    public:
        //注意,赋予不同的缺省参数值。这真糟糕
        virtual void draw(ShapeColor color = Green) const;
        ...
    };
    
    class Circle: public Shape{
    public:
        virtual void draw(ShapeColor color) const;
        ...
    };
    

    这个继承体系图如下:

    继承关系

    现在考虑这些指针:

    Shape* ps;                     //静态类型为Shape*  
    Shape* pc = new Circle;        //静态类型为Shape*
    Shape* pr = new Rectangle;     //静态类型为Shape*
    

    pspcpr都被声明为pointer-to-Shape类型,所以它们都以Shape为静态类型。不论它们真正指向什么,它们的静态类型都是Shape*。
    pc的动态类型是
    Circlepr的动态类型是Rectangleps*没有动态类型,因为它尚未指向任何对象。

    动态类型可在程序执行中改变(通常经由赋值动作):

    ps  =  pc;    //ps的动态类型如今是Circle*
    ps  =  pr;    //ps的动态类型如今是Rectangle*
    

    virtual函数根据动态绑定而来,意思是调用一个virtual函数时,究竟调用哪一份实现代码,取决于发出调用那个对象的动态类型:

    pc->draw(Shape::Red);    //调用Circle::draw(Shape::Red)
    pr->draw(Shape::Red);    //调用Rectangle::draw(Shape::Red)
    

    当然,这都是基础知识。你肯定已经掌握了。但是,上面已经提到过,virtual函数是动态绑定而缺省参数值却是静态绑定。意思是你可能会在调用一个定义于derived class内的virtual函数的同时,却使用base class为它所指定的缺省参数值:

    pr->draw( );      //调用Rectangle::draw(Shape::Red)
    

    pr的动态类型是Rectangle,所以调用的是Rectanglevirtual函数,一如你所预期。Rectangle::draw函数的缺省参数值应该是GREEN,但由于pr的静态类型是Shape,所以调用的缺省参数值来自Shape class而非Rectangle class!结局是这个函数调用有着奇怪并且几乎没人预料得到的组合,由Shape classRectangle classdraw**声明时各出一半力。

    由于书中作者给的这个例子,不是很明显,有点隐晦,可能理解上文有点吃力,但是我们换个例子代码:

    class Shape {
    public:
        virtual void draw(int color = 0) const = 0;
    };
    class Rectangle: public Shape{
    public:
        virtual void draw(int color = 1) const
        {
            cout << "Rectangle class : " << color << endl;
        }
    };
    class Circle: public Shape{
    public:
        virtual void draw(int color) const
        {
            cout << "Circle class : " << color << endl;
        }
    };
    
    int main()
    {
        Shape* pc = new Circle;        //静态类型为Shape*
        Shape* pr = new Rectangle;     //静态类型为Shape*
        pc->draw();                    //Circle class : 0
        pr->draw();                    //Rectangle class : 0
        pc->draw(10);                  //Circle class : 10
        pr->draw(10);                  //Rectangle class :10
        return 0;
    }
    

    这下大家一定很清楚了吧,就不多解释了。

    以上事实不只局限于“pspcpr都是指针”的情况;即使把指针换成references问题仍然存在。

    Circle c;
    Rectangle r;
    Shape &sc = c;
    Shape &sr = r;
    sc.draw();                    //Circle class : 0
    sr.draw();                    //Rectangle class : 0
    sc.draw(10);                  //Circle class : 10
    sr.draw(10);                  //Rectangle class :10
    

    为什么C++坚持以这种方式来运作呢?

    答案在于运行期效率。如果缺省参数值是动态绑定,编译器就必须有某种方法在运行期为virtual函数决定适当的参数缺省值。这比目前实行的“在编译期决定”的机制更慢而且更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍,其结果就是你如今所享受的执行效率。

    代码重复问题

    如果我们试着遵守本条规则,并且同时提供缺省参数给base和derived class的用户,又会发生什么事呢?

    class Shape {
    public:
        enum ShapeColor {Red, Green, Blue};
        virtual void draw(ShapeColor color = Red) const = 0;
        ...
    };
    class Rectangle: public Shape{
    public:
        virtual void draw(ShapeColor color = Red) const;
        ...
    };
    

    看起来是代码重复了,不会警告也不会报错,仅仅只是重复写了代码而已,更糟的是,代码重复又带着相依性:如果Shape内的缺省参数值改变了,所有“重复给定缺省参数值”的那些derived classes也必须改变,否则它们最终会导致“重复定义一个继承而来的缺省参数值”,与我们的本条规则的愿望相悖。应该怎么办呢?

    当你想令virtual函数表现出你所想要的行为但却遭遇麻烦,聪明的做法是考虑替代设计。条款35列了不少virtual函数的替代设计,其中之一是NVInon-virtual interface)手法:令base class内的一个public non-virtual函数调用private virtual函数,后者可被derived classes重新定义。这里我们可以让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作:

    class Shape {
    public:
        enum ShapeColor {Red, Green, Blue};
        void draw(ShapeColor color = Red) const            //如今它是non-virtual
        {
            doDraw(color);                                 //调用一个virtual
        }
        ...
    private:
        virtual void doDraw(ShapeColor color) const = 0;    //真正的工作在此处完成
    };
    class Rectangle: public Shape{
    public:
        ...
    private:
        virtual void doDraw(ShapeColor color) const;        //注意,无须指定缺省参数值。
        ...
    };
    

    NOTE:

    绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定的。

    ps:本条款在《C++ Primer》第四版 中文版 第15.2.4节的第5点——“虚函数与默认实参”有讲解,P482。但是没有举例说明,所以可能不好理解,或者给人印象不深。


    小问题:

    #include <iostream>
    #include <string>
    using namespace std;
     
     class Base {
     public:
         virtual ~Base(){}
         virtual void display() const {
             cout << "Base" << endl;
         }
     };
     
     class Derive:public Base {
     public:
        virtual ~Derive(){}
        virtual void display() {
            cout << "Derive" << endl;
        }
     
     };
     
     int main()
     {
        Base *pbase = new Derive();
        pbase -> display();
        return 0;
     }
     
    

    为啥输出?
    Base

    解答

    这个问题其实不难,关键是你没有搞清楚 重载、隐藏、覆盖三者的区别,以及const的概念。

    重载:要求函数名相同,形参列表不同,但要求两个函数必须在同一个作用域。你这道题目里的dispaly函数,一个在子类,一个在父类,显然不是在同一个作用域。故排除重载。

    覆盖:发生在父子类之间,要求父类的函数有virtual修饰,同时对于返回类型是基本类型的函数来说,要构成覆盖,必须函数名、形参列表、返回值类型全部相同,你的父类里面的display后面有个一个const修饰,他修饰的是形参列表里的this指针,也就是dispaly(const this)。这样一写,你应该明白了吧,子类里的display应该是dispaly(),显然形参列表不相同,自然就构不成覆盖啊。

    隐藏:发生在父子类之间,只要函数名相同就构成隐藏。

    纵上所述,你的这两个函数构成的是隐藏关系,而不是覆盖,既然不是覆盖,那么何来多态呢?
    所以运行结果肯定是Base。

    如果你想实现多态,有两种方法:

    • 去掉父类display函数后面的const
    • 在子类display函数后面加上const
    Effective C++

    相关文章

      网友评论

        本文标题:Item 37:绝不重新定义继承而来的缺省参数值

        本文链接:https://www.haomeiwen.com/subject/ujdvlttx.html