条款37:绝不重新定义继承而来的缺省参数值
我们只能继承两种函数:virtual和non-virtual函数。然而重新定义一个继承而来的non-virtual函数永远是错误的(条款36),所以本条款的讨论局限于“继承一个带有缺省参数值的virtual函数”。
virtual函数系动态绑定,而缺省参数值却是静态绑定。对象的所谓静态类型,就是它在程序中被声明时所采用的类型。考虑以下的class继承体系:
class Shape {
public:
enum ShapeColor {Red, Greeen, Blue};
virtual void draw(ShapeColor color = Red) const = 0;
...
};
class Rectangle: public Shape {
public:
//注意,赋予不同的缺省参数值,这很糟糕。
virtual void draw(ShapeColor color = Greeen) const;
...
};
class Circle: public Shape {
public:
virtual void draw(ShapeColor color) const;
//请注意,以上这么写则当客户以对象调用此函数,一定要指定参数值。
//因为静态绑定下这个函数并不从其基类继承缺省参数值。
//但若以指针(或引用)调用此函数,可以不指定参数值,
//因为动态绑定下这个函数会从基类继承缺省参数值
...
};
现在考虑这些指针:
Shape* ps; //静态类型为Shape*
Shape* pc = new Circle; //静态类型为Shape*
Shape* pr = new Rectangle;
对象的所谓动态类型则是指“目前所指对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。以上例而言,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函数是动态绑定的,而缺省参数值却是静态绑定。意思是你可能会在调用一个定义于派生类的virtual函数的同时,却使用基类为它所指定的缺省参数值:
pr->draw(); //调用Rectangle::draw(Shape::Red)
此例中,pr的动态类型是Rectangle*,所以调用的是Rectangel的virtual函数,但是由于pr的静态类型是Shape*,所以此调用的缺省参数值来自基类而非派生类。
为什么C++坚持以这种方式来运作呢?答案在于运行期效率。如果缺省参数值是动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的参数缺省值。这比目前实行的“在编译期间决定”的机制更慢而且更复杂,为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。
如果我们遵守这条规则,并同时提供相同的缺省参数值给基类和派生类:
class Shape {
public:
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle: public Shape {
public:
virtual void draw(ShapeColor color = Red) const;
...
};
这将造成的是代码重复,以及为今后更改缺省值埋下隐患,因为你必须同时改变基类和派生类该函数的缺省参数值。可替代的NVI手法如下:
class Shape {
public:
void draw(ShapeColor color = red) const //如今是一个non-virtual
{
doDraw(color); //调用一个virtual
}
private:
virtual void doDraw(ShapeColor color) const = 0; //真正的工作在此完成
};
class Rectangle: Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const; //不须指定缺省参数值
};
条款38:通过复合塑膜出has-a或“根据某物实现出”
复合是类型之间的一种关系,当某种类型的对象内含其他类型的对象,便是这种关系。例如:
class Address {...}; //某人的住址
class PhoneNumber {...};
class Person {
public:
...
private:
string name; //合成成分物
Address address; //同上
PhoneNumber voiceNumber; //同上
PhoneNumber faxNumber; //同上
};
请记住
- 复合的意义和public继承完全不同。
- 在应用域,复合意味着has-a。在现实域,复合意味着is-implemented-in-terms-of.