C++ 動態配置記憶體心得(上)

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

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

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

    但很多人都會犯一個錯誤,就是忘了把 class 中 data member 的部份在解構子(destructor)中清除乾淨,比方說下列這個例子:
    [code lang="cpp"]
    class MyClass {
    public:
    MyClass();
    ~MyClass();
    ...
    private:
    OtherClass *pObj;
    ...
    };
    [/code]
    MyClass 中定義了一個 private data member pObj,通常在建構子(constructor)會將它初始化,比方說:
    [code lang="cpp"]
    MyClass::MyClass() {
    pObj = new OtherClass(...);
    ...
    }
    [/code]
    結果卻忘了在 MyClass::~MyClass() 裡 delete 掉 pObj,這樣即便你釋放了由 MyClass 生出的物件,但卻讓 pObj 讓你有了 memory leak 的危機...所以一定要注意 class 中解構子是不是真的把 pObj 都清乾淨了

    不過,C++ 裡在物件的繼承卻有個很少人會注意到的特性,比方說下面有個例子:
    [code lang="cpp"]
    class BaseClass {
    public:
    BaseClass(); // ctor
    ~BaseClass(); // dtor
    ....
    private:
    ....
    };

    class ChildClass : public BaseClass {
    public:
    ChildClass(); // ctor
    ~ChildClass(); // dtor
    ...
    private:
    X *pObj; // ptr to some obj.
    ...
    };
    [/code]
    這樣的類別繼承關係很常見,我們可能也會定義一個 factory function 來產生所有繼承自 BaseClass 類別的物件,比方說像這樣:
    [code lang="cpp"]
    BaseClass* createObject() {
    // 很常見的 box
    BaseClass *pRet = new ChildClass();
    ...
    return pRet;
    }
    [/code]
    然後就會很自然地在 code 中這樣寫:
    [code lang="cpp"]
    BaseClass *pMyObj;
    ...
    pMyObj = createObject();
    ...
    delete pMyObj;
    [/code]
    問題就來了,在 createObject 這個 function 裡我們產生的 BaseClass 物件來自 ChildClass,但是我們卻 delete 一個 BaseClass 指標所指到的東西,這樣的寫法會讓 ChildClass::~ChildClass() 這個解構子沒被呼叫到而無法清乾淨 ChildClass 裡的記憶體,不過這個問題還算好解決,只要把 BaseClass 中的解構子加上 virtual 的關鍵字就可,變成這樣:
    [code lang="cpp"]
    class BaseClass {
    public:
    BaseClass();
    virtual ~BaseClass();
    ...
    };
    [/code]
    這樣當你在 delete 一個 BaseClass 物件時,會因為 BaseClass 的解構子被定義成 virtual,而透過一個 pointer 來找到繼承它的 class 來呼叫該 class 的解構子(在本例中就是呼叫 ChildClass::~ChildClass() ),如此才能順利地如你所願地把記憶體清乾淨。

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

(待續...)