类继承中默认参数值问题

 1 #include<iostream>
 2 #include<ctime>
 3 #include <stdio.h>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include <map>
 7 #include <string>
 8 using namespace std;
 9 class A
10 {
11 public:
12     A(){
13         cout << "A()" << endl;
14     }
15 
16     ~A(){
17         cout << "~A()" << endl;
18     }
19     virtual void func(int val = 1)
20     {
21         std::cout << "A->" << val << std::endl;
22     }
23 
24     virtual void test()
25     {
26         func();
27     }
28 };
29 
30 class B:public A
31 {
32 public:
33     B(){
34         cout << "B()" << endl;
35     }
36 
37     ~B(){
38         cout << "~B()" << endl;
39     }
40     void func(int val = 0)
41     {
42         std::cout << "B->" << val << std::endl;
43     }
44 };
45 
46 #if 1
47 int main(){
48     B*p = new B;
49     p->test();
50     delete p;
51 }
52 #endif
View Code

由于p指向了继承类B的对象,所以,在调用A中的test之后,虚函数func()会调用继承类B的函数,而默认形参仍然会使用test所在的类A的形参1,从而输出B->1。

 

http://www.cnblogs.com/zhoug2020/archive/2012/05/23/2514215.html

 

让我们开门见山的讨论本话题:继承一个含有默认参数值的虚函数。

此情况下,本条目的证明问题则显得十分了然:虚函数是动态绑定的,而默认参数值是静态绑定的

你说啥?静态绑定于动态绑定之间的区别已经让你头晕目眩了?(静态绑定又称早期绑定,动态绑定又称晚期绑定,这是官方说法。)我们只好复习一下了。

一个对象的静态类型就是你在对其进行声明时赋予它的类型。请考虑下面的类层次结构: 

// 几何形状类
 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;
  
};

 UML来表示:

 

现在请考虑下面的指针:

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

 示例中,pspc以及pr都声明为指向Shape的指针,因此他们的静态类型均为Shape*。请注意,这样做使得无论他们实际指向的对象是什么类型,他们的静态类型都必为Shape*

对象的动态类型是通过他当前引用的对象的类型决定的。也就是说,动态类型表明了他应具有怎样的行为。在上文的示例中,pc的动态类型是Circle*pr的动态类型是Rectangle*。而对于ps来说,他在当前根本不具备动态类型,因为他(目前)还没有引用任何对象呢。

动态类型,顾名思义,在程序运行时可能会有所改变,通常是通过赋值操作发生: 

ps = pc;               // ps当前的动态类型为Circle*
ps = pr;               // ps当前的动态类型为Rectangle*

 虚函数是动态绑定的,这就意味着,对于一个特定的函数调用,其调用对象的动态类型将决定调用这一函数的哪个版本: 

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

 我知道这些都是老 生常谈了,你当然已经对虚函数有了透彻的理解。只有在虚函数包含默认参数值时,情况才有所不同。这是因为(如上文所述),虚函数是动态绑定的,但是默认参 数是静态绑定的。这也就意味着对于一个虚函数,你可能会调用它在派生类中的定义,而默认参数值则采用基类中的值: 

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

 这种情况下,由于pr的动态类型是Rectangle*,于是此处便调用了虚函数drawRectangle版本,正如你所愿。在Rectangle::draw中,默认参数值是Green。然而,因为pr的静态类型是Shape*,这里的draw调用将采用Shape类中的默认参数值,而不是Rectangle!最终,在Shape类和Rectangle类之间,对于draw的调用必将出现混乱的无法预知的现象。

这里pspcpr是指针,然而这并不影响上文的结论。如果他们是引用的话,问题同样存在。这里只有一个重点:draw是虚函数,他的一个默认参数值在派生类中被重定义了。

为何C++在这一问题上如此倒行逆施? 答案是:运行时效率。如果默认参数值是动态绑定的话,那么编译器必须提供一整套方案,为运行时的虚函数参数确定恰当的默认值。而这样做,比起C++当前使用的编译时决定机制而言,将会更复杂、更慢。鱼和熊掌不可兼得,C++将设计的中心倾向了速度和简洁,你在享受效率的快感的同时,如果你忽略本条目的建议,你就会陷入困惑。

一切看上去似乎尽善尽美了,但是一旦你不假思索的遵守本条建议,为基类和派生类分别提供默认参数值的话,看看将会发生什么: 

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中的默认参数值改变了的话,那么所有的派生类中相应的值都必须改变。否则这些函数仍将改变继承来的默认参数值。那么怎么办呢?

遇到麻烦了?虚函数无法按照你预想的方式运行?这时候明智的做法是:考虑一个替代的设计方案,第35条中介绍了几种虚函数的替代方案。其中一种是非虚拟接口惯例方案NVI惯例):在基类中用一个公有的非虚函数调用一个私有的虚函数,并在派生类中重定义这一虚函数。在这里,我们将默认参数置于非虚函数中,让虚函数做具体的工作。

 

class Shape {
public:
  enum ShapeColor { Red, Green, Blue };
 
  void draw(ShapeColor color = Red) const     // 现在draw是非虚函数
  {
    doDraw(color);                            // 调用一个虚函数
  }
 
  
 
private:
  virtual void doDraw(ShapeColor color) const = 0;
                                              // 这个函数做真正的工作
};
 
class Rectangle: public Shape {
public:
 
  
 
private:
  virtual void doDraw(ShapeColor color) const//此处不需要默认参数值
  
};

 这一设计方案使得draw函数中color参数的缺省值永远为Red

铭记在心

·避免在对函数中继承得来的默认参数值进行重定义,这是因为默认参数值是静态绑定的,而(派生类中唯一一类可以重定义的)虚函数是动态绑定的。

转载于:https://www.cnblogs.com/guxuanqing/p/5854724.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值