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

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的动态类型是Circle*pr的动态类型是Rectangle*ps没有动态类型,因为它尚未指向任何对象。

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

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

时间: 2024-12-16 00:02:22

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

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

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要重新而来的缺省参数值,因为缺省参数值都是静态绑定,而 virtual 函数 -- 你唯一应该覆写的东西 -- 却是动态绑定 示例: class Shape{ public: enum ShapeColor {Red, Green, Blue}; virtual void draw(ShapeColor color = Red) const = 0; }; class Rect

Effective C++:条款37:绝不重新定义继承而来的缺省参数值

由于重新定义继承而来的non-virtual函数是不正确的(见上一个条款),所以这个条款就将问题局限于:绝不重新定义继承一个带有缺省参数值的virtual函数. (一) virtual函数是动态绑定的,而缺省参数却是静态绑定. 对象的所谓静态类型,是它在程序中被声明时所采用的类型. 你可能会在"调用一个定义于derived class 内的virtual函数"的同时,却使用了base class为它所指定的缺省参数值. (二) 为什么继承而来的virtual函数的缺省参数值不能被重新定

effective C++中条款37:绝不重新定义继承而来的缺省参数值

virtual 函数会动态绑定,而virtual函数的缺省参数值是静态绑定的.用一个base类型的指针p去指向一个derived类对象,通过p调用虚函数时,会动态绑定到实际所指对象中的函数:用一个derived类型的指针p2指向一个derived对象,由p2调用函数时,直接就是调用的derived中的函数,其参数值也是derived类中函数对应的参数值. #include <iostream> using namespace std; class A { public: enum Color

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

考虑如下的代码: 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

Effective C++ Item 36 绝不重新定义继承而来的 non-virtual 函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要重新定义继承而来的 non-virtual 函数 --> Item 7 "为多态基类声明 virtual 析构函数" 是本条款的特例 示例: class B{ public: void mf(); //... }; class D: public B{ public: void mf(); // 遮掩了B::mf,Item 33 名称遮掩规则 } D x; B

C++ 静态绑定与动态绑定------绝不重新定义继承而来的缺省参数

在了解静态绑定和动态绑定之前,先了解什么是对象的静态类型,什么是对象的动态类型. 对象的静态类型:对象在声明时采用的类型.是在编译器决定的. 对象的动态类型:目前所指对象的类型.是在运行期决定的. 动态类型可以更改,而静态类型不可更改.看一个示例 class Base { public: void setData(int i=10) { cout <<" virtual int Base::setData()"<<endl; } virtual int getD

条款38: 决不要重新定义继承而来的缺省参数值

虚函数是动态绑定而缺省参数值是静态绑定的,当基类和派生类对同一个虚函数设置缺省参数值时,只有基类的缺省参数值起作用. 对象的静态类型是指你声明的存在于程序代码文本中的类型,对象的动态类型是由它当前所指的对象的类型决定的.即,对象的动态类型表示它将执行何种行为. 虚函数是动态绑定的,意思是说,虚函数通过哪个对象被调用,具体被调用的函数就由那个对象的动态类型决定 将虚函数和缺省参数值结合起来分析就会产生问题,因为,如上所述,虚函数是动态绑定的,但缺省参数是静态绑定的.这意味着你最终可能调用的是一个定

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

在继承中,分为两类函数:virtual和non-virtual.而重新定义一个非虚函数是不好的(条款36),那么以下的讨论就是如何定义继承而来的虚函数. 强调:虚函数是动态绑定的,而缺省参数值是静态绑定的. 1 #include <iostream> 2 3 class Shape 4 { 5 public: 6 enum ShapeColor{ Red, Green, Blue }; 7 virtual void draw(ShapeColor color = Red) const = 0;

Effective C++:条款36:绝不重新定义继承而来的non-virtual函数

(一)首先有下面的继承体系: class B { public: void mf(); ... }; class D : public B {...}; D x; 以下行为: B* pB = &x; pB->mf(); 异于以下行为: D* pD = &x; pD->mf(); 上面两种行为产生的结果不一定相同.看下面这种情况: mf是个non-virtual函数而D定义有自己的mf版本: class D : public B { public: void mf(); ...