由於重新定義繼承而來的non-virtual函數是不正確的(見上一個條款),所以這個條款就將問題局限於:絕不重新定義繼承一個帶有缺省參數值的virtual函數。
(一)
virtual函數是動態綁定的,而缺省參數卻是靜態綁定。
對象的所謂靜態類型,是它在程序中被聲明時所采用的類型。
你可能會在“調用一個定義於derived class 內的virtual函數”的同時,卻使用了base class為它所指定的缺省參數值。
(二)
為什麼繼承而來的virtual函數的缺省參數值不能被重新定義呢?
其實原因也挺簡單:缺省參數是靜態綁定,而virtual函數是動態綁定. 所謂對象的靜態綁定也叫前期綁定,它是說該對象類型和行為在程序編譯期間就可以確定,例如:
class Shape{ public: enum Color{RED,GREEN,BLUE}; virtual void draw(Color color = RED)const = 0; ... }; class Circle : public Shape{ public: //哦歐! 竟然改變缺省參數值 virtual void draw(Color color = GREEN)const{ ... } }; class Rectangle : public Shape{ public: //沒用指定參數類型,需要用戶去明確的指定其值 //靜態綁定下不繼承基類的缺省值,若以指針或引用調用則不需要指定缺省值,因為動態綁定 //繼承基類的參數缺省值 virtual void draw(Color color)const{ ... } };看一下下面幾個指針:
Shape* ps; Shape* pc = new Circle; Shape* pr = new Rectangle;這裡的ps,pc,pr不管它具體指向的是什麼對象,他們的靜態類型都是Shape*。而動態類型就是它們真正指向的對象的類型。故pc的動態類型為Circle*,而pr的動態類型為Rectangle*,ps由於沒有指向任何對象,所以此時沒有動態類型。
(三)看下面這個語句!
pc->draw(); //注意調用的是: Circle::draw(RED)怎麼會調用Circle::draw(RED)呢!?為什麼不是Circle::draw(GREEN)?
(1)首先根據其調用語句用指針這一事實,我們就知道了其調用的版本應該是該指針的動態類型的函數版本,即Circle::draw,這個問題不大。
(2)下面我們來看它的傳值參數,前面我們提到缺省參數值是靜態綁定的,而pc的靜態類型是Shape*,所以該參數的傳入值是Shape的該函數版本的缺省值。
那為什麼C++堅持以這種乖張的方式來運作呢?答案在於運行期效率,如果缺省值也是動態綁定的,那麼編譯期就必須要有辦法在運行期為virtual函數決定適當的參數缺省值.如果這樣做的話,就要比目前實現的"在編譯期決定"的機制更慢而且更復雜,考慮到執行速度和實現上的簡易性,C++放棄了這樣的做法。
(四)解決方法!
現在,為了遵循本款約定卻同時提供缺省參數值給你的基類和父類,,代碼就這樣了:
class Shape{ public: enum Color{RED,GREEN,BLUE}; virtual void draw(Color color = RED)const = 0; ... }; class Circle:public Shape{ public: virtual void draw(Color color = RED)const {...} };明顯的是代碼重復嘛!何況你要是想改變缺省值的話,必須要同時改變基類和子類函數的缺省值,一不小心,就會出現漏改或寫錯的情況,導致意想不到的錯誤出現.有沒用一種更方便的寫法呢?當然,你還記得NVI手法嗎?額..,(non-virtual interface),要是忘記的話,回過頭看看條款35,用這種手法的話,我們寫下代碼如下:
class Shape{ public: enum Color{RED,GREEN,BLUE}; void draw(Color color = RED) const{ ... doDraw(color); ... } ... private: virtual void doDraw(Color color) const = 0; }; class Circle:public Shape{ ... private: virtual void doDraw(Color color){ ... } };由於draw是non-virtual而non-virtual絕對不會被重新改寫(條款36),所以color的缺省值總是為RED。
請記住:
(1)絕對不要重新定義一個繼承而來的缺省參數值,因為缺省參數值都是靜態綁定,而virtual函數-你唯一應該覆寫的東西-卻是動態綁定。