設計類(class)的時候,你可能會幹下面這幾件事情:程序員
1.只讓繼承類(derived class)繼承成員函數的接口;api
2.讓derived class同時繼承函數的接口和實現,但又能覆寫所繼承的實現;函數
3.同時繼承函數的接口和實現,但不容許覆寫任何東西;spa
如下面的類爲例,來解釋上面的三種實現:設計
class Shape { public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; ... }; class Rectangle: public Shape{...}; class Ellipse: public Shape{...};
對於draw函數,其實咱們是能夠調用的:以下:code
Shape *ps = new Shpae; //Error! Shape is abstract Shape *ps1 = new Rectangle; //ok ps1->draw(); //call Rectangle::draw Shape *ps2 = new Ellipse; //ok ps2->draw(); //Ellipse::draw ps1->Shape::draw(); //Shape::draw ps2->Shpae::draw(); //Shape::draw
對於Shape::error這個函數,爲非純虛函數,他告訴繼承類,你若是不想本身寫一個,則會調用基類的缺省版本。blog
這種容許非純虛函數的雙面性行爲,可能形成危險。例如,若是有個航空公司,該公司有A型和B型兩種飛機,二者以相同的方式飛行,設計繼承體系以下:繼承
class Airport {...}; class Airplane { public: virtual void fly(const Airport &destination); ... }; void Airplane::fly(const Airport &destination) { //fly to the destination. } class ModelA: public Airplane {...}; class ModelB: public Airplane {...};
如今,假設該公司購買了一種新式飛機C,飛行模式有些不一樣,該公司的程序員在繼承體系中針對這種飛機添加了一個class,假如他們忘記了定義其fly函數:接口
class ModelC: public Airplane { ...//not implementing fly() };
調用的時候,你會採用以下的操做:ip
Airport BCIA(...); //Beijing Capital International Airport; Airplane *pa = new ModelC; ... pa->fly(BCIA); //call Airplane::fly
上面的程序試圖以ModelA和ModelB的飛行方式來飛ModelC,用噴氣式飛機的方式來飛螺旋槳飛機,要出人命啊。
問題不在於Airplance::fly有缺省行爲,而在於ModelC在未說明「我要」的狀況下就繼承了該缺省行爲。
咱們能夠避免這種作法:
class Airplane { public: virtual void fly(const Airport &destination) = 0; ... protected: void defaultFly(const Airport &destination); }; void Airplane::defaultFly(const Airport &destination) { //implementation of flying. }
將Airplane::fly改成一個pure virtual函數,這樣的話,每一個繼承類都要本身實現fly:
class ModelA: public Airplane { public: virtual void fly(const Airport &destination) { defaultFly(destination); } ... }; class ModelB: public Airplane { public: virtual void fly(const Airport &destination) { defaultFly(destination); } ... };
如今ModelC必須本身實現fly函數來,嘿嘿:
class ModelC: public Airplane { virtual void fly(const Airport &destination); ... }; void ModelC::fly(const Airport &destination) { //ModelC's way. }
這種方式可能由於使用者的複製粘貼而錯誤,可是比以前有了保障。至於Airplane::defaultFly,如今設置成了protected,由於他是繼承類的實現方式,用戶只在乎飛機能不能飛,不在乎怎麼飛的。
固然,如今這種方式也有個問題,多定義了一個defaultFly,可能會致使命名空間污染的問題。咱們能夠用下面這種方式,就是純虛函數必須在繼承類中從新聲明,可是他們能夠擁有本身的實現。例如:
class Airplane { public: virtual void fly(const Airport &destination) = 0; }; void Airplane::fly(const Airport &destination) { //do flying; } class ModelA: public Airplane { public: virtual void fly(const Airport &destination) { Airplane::fly(destination); } ... }; class ModelB: public Airplane { public: virtual void fly(const Airport &destination) { Airplane::fly(destination); ... } }; class ModelC: public Airplane { public: virtual void fly(const Airport &destination); ... }; void ModelC::fly(const Airport &destination) { //do ModelC's flying; }
最後,對於objectID這個非虛函數,意味着在繼承類中,不須要對其進行修改。因此在繼承類中不建議從新定義。這個會在下個議題中闡述。