2013年6月1日 星期六

pure virtual function (C++)

有些時後會發生一種情況,我們必須創立許多類似的類別,
而這些類別必須有某些相同且必要的條件,
同樣以遊戲為例,例如資深工程師制訂了NPC的規格,
各工程師照著規格實作遊戲企劃人員構想的各種NPC
這種情況可以用到virtual function來解決各衍生類別的同名異式,
但無法要求衍生類別一定要實作某些類別的內容,
這時可以利用"pure virtual function(純虛擬函式)"

class CNPC    進而讓CNPC這個class變成abstract class,
 {
  public:
  int Attack() const = 0;
( = 0是pure virtual funcion的關鍵字)
  int input (float inValue) =0  // 不能使用const,使用了const就變成惟讀函數
};


在原型的尾端接上 = 0 ,就可以使其成為純虛擬函數,
若一個類別中含有pure virtual function,它是無法用來產生物件的,
此種類別只能被當作基底類別,即使我們有在實作檔案中定義詳細的內容,
抽象基礎類別(abstract base class)即是這類的應用。


若資深工程師定義了一個詳細的NPC基底類別讓其他工程師繼承,
他除了要將函數宣告為純虛擬函數外,還有一項重點要注意,
他可能必須適時的使用protected關鍵字。

大部份的時間我們只會用到private與public來區分類別成員的保護性,
但所有衍生類別卻只能透過基底類別的公用函數來存取private member
實務上並不方便,也非必要,因為基底類別並沒辦法用來宣告,
所以我們可以將一些member variable宣告為protected成員,
使其成為衍生類別的private member。

 class CNPC
{
private:
  int m_nHP;
  int m_nMP;
protected:
  void Sleep() const = 0;
public:
  int Attack() const = 0;
};


NPC的休息動作、HP回復速度等內容要求衍生類別必須自行定義,
利用protected宣告此函數,如此衍生NPC即可擁有私有的休息函數,
又可以達到外界又無法呼叫此休息函數,僅由類別內自己控制。

若此Sleep()沒有宣告為protected而是宣告為private member,
那麼CNPC就必須要提供一個public function讓衍生類別間接呼叫休息函數,
可以做到同樣的效果,但較為麻煩且不必要。


一個類別中如果含有純虛擬函式,則該類別為一「抽象類別」(Abstract class),該類別只能被繼承,而不能用來直接生成實例,如果試圖使用一個抽象類別來生成實例,則會發生編譯錯誤。

對於需要制訂清楚的規則與關係時,abstract base class設計是個不錯的方法。 




析構函數也可以是虛的,甚至是純虛的,但是構造函數不能是虛的
     

純虛的析構函數並沒有什麼作用,是虛的就夠了。通常只有在希望將一個類變成抽象類(不能實例化的類),而這個類又沒有合適的函數可以被純虛化的時候,可以使用純虛的析構函數來達到目的。構造函數不能是虛的(為什麼?因為在一個構造函數調用期間,虛機制並不工作),但是你可以可能通過虛函數 virtual clone()(對於拷貝構造函數)或虛函數 virtual create()(對於默認構造函數),得到虛構造函數產生的效果。

另外 抽象類別 (Abstract classes)也可以使用struct。

根据C++语言标准,在C++中,除struct默认成员是公有的,class默认成员是私有的外,struct和class没有区别。


如下:
class Shape {  // 這是一個
抽象類別 (Abstract classes) 
public:
   virtual ~Shape() { }                 
// 虛析構函數
   virtual void draw() = 0;             
// 純虛函數
   virtual void move() = 0;
   
// ...
   virtual Shape* clone()  const = 0;   
// 使用拷貝構造函數
   virtual Shape* create() const = 0;   
// 使用默認構造函數
 };

 class Circle : public Shape { // Shape被繼承
 public:
   Circle* clone()  const { return new Circle(*this); }
   Circle* create() const { return new Circle();      }
   
// ...
 };
    


在 clone() 成員函數中,代碼 new Circle(*this) 調用 Circle 的拷貝構造函數來複製this的狀態到新創建的Circle對象。

在 create()成員函數中,代碼 new Circle() 調用Circle的默認構造函數。
 

在Circle中的clone及create將會覆蓋Shape中的clone及create這兩個Function。

因此 呼叫 class Shape 的函數 area() 會用到 Class Circle 的 函數 area()

此處可參考 http://nknucc.nknu.edu.tw/~jwu/c/cpgch16.htm
查閱抽象類別的說明


用戶將它們看作「虛構造函數」來使用它們:
 void userCode(Shape& s)
 {
   Shape* s2 = s.clone();
   Shape* s3 = s.create();
   
// ...
   delete s2;    
// 在此處,你可能需要虛析構函數
   delete s3;
 }
    這個函數將正確工作,而不管 Shape 是一個CircleSquare,或是其他種類的 Shape,甚至它們還並不存在。



另一個例子
#include
#include
using namespace std;

class Pet {
public:
    virtual string speak() const { return "Pet!"; }
};

class Dog : public Pet {
public:
  string speak() const { return "Bark!"; }
};

class Cat : public Pet {
public:
  string speak() const { return "Miao!"; }
};

void doSpeak(Pet &animal){
    cout << animal.speak() << endl;
}

int main() {
    Dog dog;
    Cat cat;
    Pet pet;
  
    //Late binding for both:
    doSpeak(dog);
    doSpeak(cat);
    doSpeak(pet);

    return 0;
}
==================
output結果:

Bark!
Miao!
Pet!
==================
若Pet中的speak( ) 不宣告為virtual,則output:

Pet!
Pet!
Pet!
==================
Dog跟Cat是繼承自Pet這個base class,

經由virtual的宣告,可以在doSpeak中藉由傳進來的類別(Dog or Cat)判斷要做誰的doSpeak();

若Dog或Cat中,不去override speak這個function,當然都會output: Pet!

若將Pet中的speak() 改寫成virtual string speak() const  = 0;

( = 0是pure virtual funcion的關鍵字)

則該virtual function會變成pure virtual function,

進而讓Pet這個class變成abstract class,

在程式中宣告任何Pet的Instance都會造成compile error,

一定要有derived class才能去宣告該class的instance。


參考網址
http://ascii-iicsa.blogspot.tw/2010/08/pure-virtual-functionabstract-base.html

http://blog.xuite.net/coolflame/code/16605599

http://iceis.pixnet.net/blog/post/2046006-virtual-function-in-c%2B%2B

http://openhome.cc/Gossip/CppGossip/PureVirtualFunction.html

http://stackoverflow.com/questions/9029548/abstract-base-struct-in-c

沒有留言: