這篇文章的出現,主要是回答一些朋友問題時,以及自己在寫程式時的小小心得。

  • 出來要,遲早要還的…而且要還得乾乾淨淨

    只要是你使用 new 來要記憶體,一定要用 delete 把要來的記憶體歸還(同理,如果是用 new[] 要來的,就要用 delete [] 來還)。

    但很多人都會犯一個錯誤,就是忘了把 class 中 data member 的部份在解構子(destructor)中清除乾淨,比方說下列這個例子:

    class MyClass {
      public:
        MyClass();
        ~MyClass();
        ...
      private:
        OtherClass *pObj;
        ...
    };

    MyClass 中定義了一個 private data member pObj,通常在建構子(constructor)會將它初始化,比方說:

    MyClass::MyClass() {
      pObj = new OtherClass(...);
      ...
    }

    結果卻忘了在 MyClass::~MyClass() 裡 delete 掉 pObj,這樣即便你釋放了由 MyClass 生出的物件,但卻讓 pObj 讓你有了 memory leak 的危機…所以一定要注意 class 中解構子是不是真的把 pObj 都清乾淨了

    不過,C++ 裡在物件的繼承卻有個很少人會注意到的特性,比方說下面有個例子:

    class BaseClass {
      public:
        BaseClass(); // ctor
    
        ~BaseClass(); // dtor
    
        ....
      private:
        ....
    };
    
    class ChildClass : public BaseClass {
      public:
        ChildClass(); // ctor
    
        ~ChildClass(); // dtor
    
        ...
      private:
        X *pObj;  // ptr to some obj.
    
        ...
    };

    這樣的類別繼承關係很常見,我們可能也會定義一個 factory function 來產生所有繼承自 BaseClass 類別的物件,比方說像這樣:

    BaseClass* createObject() {
      // 很常見的 box
      BaseClass *pRet = new ChildClass();
      ...
      return pRet;
    }

    然後就會很自然地在 code 中這樣寫:

    BaseClass *pMyObj;
    ...
    pMyObj = createObject();
    ...
    delete pMyObj;

    問題就來了,在 createObject 這個 function 裡我們產生的 BaseClass 物件來自 ChildClass,但是我們卻 delete 一個 BaseClass 指標所指到的東西,這樣的寫法會讓 ChildClass::~ChildClass() 這個解構子沒被呼叫到而無法清乾淨 ChildClass 裡的記憶體,不過這個問題還算好解決,只要把 BaseClass 中的解構子加上 virtual 的關鍵字就可,變成這樣:

    class BaseClass {
      public:
        BaseClass();
        virtual ~BaseClass();
      ...
    };

    這樣當你在 delete 一個 BaseClass 物件時,會因為 BaseClass 的解構子被定義成 virtual,而透過一個 pointer 來找到繼承它的 class 來呼叫該 class 的解構子(在本例中就是呼叫 ChildClass::~ChildClass() ),如此才能順利地如你所願地把記憶體清乾淨。

    參考書目:Effective C++, 3/e

(待續…)

 

歷史上的今天

我要留言
(必填)
(必填)