美文网首页C++
[C++] 动态绑定和静态绑定

[C++] 动态绑定和静态绑定

作者: 何幻 | 来源:发表于2017-05-04 16:08 被阅读78次

    你不应该重新定义一个继承而来的non-virtual函数。
    为继承而来的virtual函数,重新定义缺省参数值,也很糟糕。

    因为,virtual函数系动态绑定(dynamically bound),
    而缺省参数值却是静态绑定(statically bound)。

    1. 静态类型

    对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型,
    考虑以下的class继承体系:

    // 一个用以描述几何形状的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 = Green) const;
    
        ...
    };
    
    class Circle: public Shape{
    public:
        virtual void draw(ShapeColor color) const;
    };
    

    现在考虑这些指针:

    // 以下三个指针ps,pc,pr的静态类型,都是Shape*
    Shape* ps;
    Shape* pc = new Circle;
    Shape* pr = new Rectangle;
    

    2. 动态类型

    对象的所谓动态类型(dynamic type)则指的是“目前所指对象的类型”,
    也就是说,动态类型可以表现出一个对象将会有什么行为。
    以上例而言,pc的动态类型是Circle*,pr的动态类型是Rectangle*,ps没有动态类型,因为它尚未指向任何对象。

    动态类型一如其名称所示,可在程序执行过程中改变(通常是经由赋值动作):

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

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

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

    3. 缺省参数值

    但是当你考虑带有缺省参数值的virtual函数,花样来了,
    virtual函数是动态绑定,而缺省参数值却是静态绑定。
    意思是,你可能会在“调用一个定义于derived class内的virtual函数”的同时,却使用base class为它所指定的缺省参数值。

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

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

    以上事实不只局限于“ps,pc和pr都是指针”的情况,即使把指针换成reference问题仍然存在。
    重点在于draw是个virtual函数,而它的缺省参数值在derived class中被重新定义了。

    这一切还好,但如果你试着遵守这条规则,并且同时提供缺省参数值给base和derived class的用户,又会发生什么事呢?

    class Rectangle: public Shape{
    public:
        virtual void draw(ShapeColor color = Red) const;
        ...
    };
    

    喔欧,代码重复。更糟糕的是,代码重复又带有相依性(with dependencies)。
    如果Shape内的缺省参数值改变了,所有的“重复给定缺省参数值”的那些derived class也必须改变,
    否则它们最终会导致“重复定义一个继承而来的缺省参数值”。
    怎么办?

    4. non-virtual interface

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

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

    由于non-virtual函数绝对不应该被derived class覆写,这个设计很清楚的使用draw函数的color缺省值总是为Red。


    Effective C++ - P180

    相关文章

      网友评论

        本文标题:[C++] 动态绑定和静态绑定

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