2010年8月30日 星期一

繼承與物件建構、解構注意事項

繼承時有幾點重要的注意事項,例如建構、解構的順序、方式、動態記憶體配置等都是需要注意的地方。

此為一個名為 CDog 的基底類別


class CDog
{
private:
  int m_nVolume; // 吠的音量
  CString m_strName; // 狗的名字
public:
  CDog() {m_nVolume = 0; strName = _T("NoName");};
  CDog(int nVol, const CString &strName)
  {
    m_nVolume = nVol;
    m_strName = strName;
  };
  virtual ~CDog();
  virtual void Bark() const = 0;
};


此為名為 CBeagle (米格魯)的衍生類別


class CBeagle : public CDog
{
enum BEAGLETYPE {BT_NONE, BT_GUIDE, BT_DRUG}; // 導盲或緝毒等種類
private:
  BEAGLETYPE m_dtType; // 種類
public:
  CBeagle() : CDog() {m_dtType = BT_NONE;};
  CBeagle(int nVol, const CString &strName, BEAGLETYPE bt) : CDog(nVol, strName) {m_dtType = BT_NONE;};
  ~CBeagle();
  void Bark();
};


這裡可以注意到,衍生類別的建構式後面接著 : 與基底類別的建構式,而執行順序是由 : 右側執行完才到左側建構衍生類別,因為類別繼承的建構順序為基底類別先建構、衍生類別後建構,解構則相反,衍生類別先解構、再解構基底類別,理由很直觀,衍生類別建構時可能會需要基底類別的成員,所以後建,衍生類別自行解構自己才有的成員,基底類別的成員再交由基底類別去處理解構,而基底類別的解構子往往會在前面加上 virtaul 關鍵字,代表其為虛擬函式,這也是有其必要性的,


int main()
{
  CDog *pDog = new CBeagle;
  return TRUE;
}


上面的程式碼在結束時,因為 pDog 為 CDog 型態指標,若解構式沒有宣告為 virtual,則會執行 CDog 的解構子,可能造成 CBeagle 的成員解構不完全而產生 memory leak,所以利用 virtaul 關鍵字使解構式可以依指標成員所指的型態執行正確的函式,即是 CBeagle 的解構式,而解構完 CBeagle 解構式時自然會去呼叫基底類別的解構式,因為預設的解構函數本來就含有呼叫基礎類別的解構函數。

利用 : 來連接基底類別建構式是很重要的,若省略這個動作,將會只將 CBeagle 的 m_dtType 做初始化造成許多處理值的問題。

另外一個重要觀念即是基底類別的指標可以指向任何衍生自它的類別記憶體位址,但只能使用基底成員本身所擁有的類別成員變數或函數。

另外若你的類別中含有動態記憶體配置成員如下


class CDog
{
private:
  TCHAR *tcName; // 狗的名字
public:
  virtual ~CDog(delete tcName;);
};

class CBeagle : public CDog
{
};


這種情況衍生類別不需要做什麼特別的事,即便不寫解構式也沒關係,因為編譯器自然會替我們產生一個預設什麼事都不做的解構式,這個解構式中並非真的什麼事都不做,它會去呼叫基礎類別的解構式,所以將 CDog 自己的指標交給它自己去 delete 吧,例如你的程式碼長醬:


class CDog
{
private:
  TCHAR *tcName; // 狗的名字
public:
  virtual ~CDog(delete [] tcName;);
};

class CBeagle : public CDog
{
private:
  TCHAR *tcUnitName; // 所屬單位名稱
public:
  ~CBeagle() {delete [] tcUnitName;};
};


這時侯,就要記得務必在衍生類別中實作自己的解構子清掉自己才有的成員嘍。

沒有留言:

張貼留言