你不應(yīng)該重新定義一個繼承而來的non-virtual函數(shù)。
為繼承而來的virtual函數(shù)冤吨,重新定義缺省參數(shù)值哑子,也很糟糕。
因為鞋喇,virtual函數(shù)系動態(tài)綁定(dynamically bound)声滥,
而缺省參數(shù)值卻是靜態(tài)綁定(statically bound)。
1. 靜態(tài)類型
對象的所謂靜態(tài)類型(static type)侦香,就是它在程序中被聲明時所采用的類型落塑,
考慮以下的class繼承體系:
// 一個用以描述幾何形狀的class
class Shape{
public:
enum ShapeColor { Red, Green, Blue };
// 所有形狀都必須提供一個函數(shù),用來繪制出自己
virtual void draw(ShapeColor color = Red) const = 0;
...
};
class Rectangle: public Shape{
public:
// 注意罐韩,賦予不同的缺省參數(shù)值憾赁,這真糟糕
virtual void draw(ShapeColor color = Green) const;
...
};
class Circle: public Shape{
public:
virtual void draw(ShapeColor color) const;
};
現(xiàn)在考慮這些指針:
// 以下三個指針ps,pc,pr的靜態(tài)類型,都是Shape*
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;
2. 動態(tài)類型
對象的所謂動態(tài)類型(dynamic type)則指的是“目前所指對象的類型”散吵,
也就是說龙考,動態(tài)類型可以表現(xiàn)出一個對象將會有什么行為。
以上例而言矾睦,pc的動態(tài)類型是Circle*
晦款,pr的動態(tài)類型是Rectangle*
,ps沒有動態(tài)類型枚冗,因為它尚未指向任何對象缓溅。
動態(tài)類型一如其名稱所示,可在程序執(zhí)行過程中改變(通常是經(jīng)由賦值動作):
// ps的動態(tài)類型如今是Circle*
ps = pc;
// ps的動態(tài)類型如今是Rectangle*
ps = pr;
virtual函數(shù)系動態(tài)綁定而來赁温,意思是調(diào)用一個virtual函數(shù)時肛宋,究竟調(diào)用哪一份函數(shù)實現(xiàn)代碼州藕,
取決于發(fā)出調(diào)用的那個對象的動態(tài)類型。
// 調(diào)用Circle::draw(Shape::Red)
pc->draw(Shape::Red);
// 調(diào)用Rectangle::draw(Shape::Red)
pr->draw(Shape::Red);
3. 缺省參數(shù)值
但是當(dāng)你考慮帶有缺省參數(shù)值的virtual函數(shù)酝陈,花樣來了,
virtual函數(shù)是動態(tài)綁定毁涉,而缺省參數(shù)值卻是靜態(tài)綁定沉帮。
意思是,你可能會在“調(diào)用一個定義于derived class內(nèi)的virtual函數(shù)”的同時贫堰,卻使用base class為它所指定的缺省參數(shù)值穆壕。
// 調(diào)用Rectangle::draw(Shape::Red)
pr->draw();
此例之中,pr的動態(tài)類型是Rectangle*
其屏,所以調(diào)用的是Rectangle
的virtual函數(shù)喇勋,一如你所預(yù)期,
Rectangle::draw
函數(shù)的缺省參數(shù)值應(yīng)該是Green
偎行,但由于pr的靜態(tài)類型是Shape*
川背,
所以,此一調(diào)用的缺省參數(shù)值來自Shape class而非Rectangle class蛤袒。
結(jié)局是這個函數(shù)調(diào)用有著奇怪并且?guī)缀踅^對沒人預(yù)料得到的組合熄云,由Shape class和Rectangle class的draw聲明式各出一半力。
以上事實不只局限于“ps,pc和pr都是指針”的情況妙真,即使把指針換成reference問題仍然存在缴允。
重點在于draw是個virtual函數(shù),而它的缺省參數(shù)值在derived class中被重新定義了珍德。
這一切還好练般,但如果你試著遵守這條規(guī)則,并且同時提供缺省參數(shù)值給base和derived class的用戶锈候,又會發(fā)生什么事呢薄料?
class Rectangle: public Shape{
public:
virtual void draw(ShapeColor color = Red) const;
...
};
喔歐,代碼重復(fù)晴及。更糟糕的是都办,代碼重復(fù)又帶有相依性(with dependencies)。
如果Shape內(nèi)的缺省參數(shù)值改變了虑稼,所有的“重復(fù)給定缺省參數(shù)值”的那些derived class也必須改變琳钉,
否則它們最終會導(dǎo)致“重復(fù)定義一個繼承而來的缺省參數(shù)值”。
怎么辦蛛倦?
4. non-virtual interface
當(dāng)你想令virtual函數(shù)表現(xiàn)出你所想要的行為但卻遭遇麻煩歌懒,聰明的做法是考慮替代設(shè)計。
其中之一是NVI(non-virtual interface)手法:
令base class內(nèi)的一個public non-virtual函數(shù)調(diào)用private virtual函數(shù)溯壶,后者可被derived class重新定義及皂。
這里我們可以讓non-virtual函數(shù)指定缺省參數(shù)甫男,而private virtual函數(shù)負(fù)責(zé)真正的工作。
class Shape{
public:
enum ShapeColor { Red, Green, Blue };
// 如今它是non-virtual
void draw(ShapeColor color = Red) const {
// 調(diào)用一個virtual
doDraw(color);
}
...
private:
// 真正的工作在此處完成
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle: public Shape{
public:
...
public:
// 注意验烧,不須制定缺省參數(shù)值
virtual void doDraw(ShapeColor color) const;
};
由于non-virtual函數(shù)絕對不應(yīng)該被derived class覆寫板驳,這個設(shè)計很清楚的使用draw函數(shù)的color缺省值總是為Red。