撰寫copy ctor和assignment operator

當你的class有管理資源(memory、file handle、mutex等)的時候,你必須在dtor之中釋放這些資源,若你的class有被拷貝的需求,那就應該要顯式(explicit)的實作copy ctor和assignment operator,因為如果你不去實作它們,compiler會替你隱式生成;而compiler隱式生成的copy ctor和assignment operator會造成許多資源管理上的問題。

Note:
Google C++ Style Guide指出,其實只有少部分的class有被拷貝的需求,當你不希望你的class被拷貝的話,就應該將copy ctor和assignment operator宣告為private,以免被誤用。因為Compiler隱式生成的copy ctor和assignment operator只作簡單的shallow copy,當你的class中沒有管理任何資源時,它們並不會帶來任何的問題;一但你在class中管理資源的話,使用copy ctor和assignment operator將會造成以下問題:

===== Example 1:不同的instances卻佔有相同的資源 =====

class CFoo
{
public:
    CFoo(int n) {
        m_ptr = new int (n);
    }

    CFoo() {
        delete m_ptr;
    }

    void ChangeValue(int n) {
        if (m_ptr) {
            *m_ptr = n;
        }
    }

    void PrintValue() {
        if (m_ptr) {
            cout << "value: " << *m_ptr << endl;
        }
    }

private:
    int* m_ptr;
};

void main()
{
    CFoo a(10);
    CFoo b = a;

    b.ChangeValue(15);
    a.PrintValue();
}

在Example 1中,console window的輸出為value: 15。原因如下:
我們在CFoo的ctor中獲取資源,為了避免leak,我們必須在dtor中釋放資源(delete時不需要檢查m_ptr是否為NULL,因為delete的底層會幫我們檢查這件事)。但是當我們執行到CFoo b = a;這行時,會呼叫CFoo的copy ctor,而Compiler隱式產生的copy ctor會幫我們作member data的shallow copy,而造成a和b的m_pt指向同一塊在heap上的記憶體。所以當我們將b的值改變為15時會影響到a的值。這也就是為什麼a.PrintValue();會輸出15的原因。

===== Example 2:造成懸置指標(dangling pointer) =====

void main()
{
    CFoo a(10);

    {
        CFoo b = a;

    }// 'b' out of scope, dtor of 'b' is called

    //now m_ptr of 'a' is a dangling pointer
}

在Example 2中,當b out of scope時,b的dtor將會被呼叫,b的m_ptr也因此被delete。但是因為a和b的m_ptr都指向同一塊記憶體,所以在b的m_ptr被delete的同時,a的m_ptr所指向的那塊記憶體也被回收了,所以此時a的m_ptr就變成了指向垃圾值的dangling pointer。

===== Example 3:引發未定義行為(undefined behavior) =====

void main()
{
    {
        CFoo a(10);

        {
            CFoo b = a;

        }// 'b' out of scope, dtor of 'b' is called

        //now m_ptr of 'a' is a dangling pointer

    }// 'a' out of scope, dtor of 'a' is called
}

在Example 2中,當a out of scope時,a的dtor將會被呼叫,a的m_ptr也因此被delete。但是在這之前,a的m_ptr所指向的那塊記憶體已經在b out of scope時被delete了;這表示在a out of scope時,我們會對同一塊記憶體delete兩次,而根據C++ standard的定義,對同一塊記憶體delete兩次會造成未定義行為(undefined behavior)。

===== Example 4:引發memory leak =====

void main()
{
    CFoo a(10);
    CFoo b(15);

    a = b;
}

在Example 4中,一開始a和b的m_ptr各自指向自己new出來的那一塊記憶體,但是在a = b;之後,a和b的m_ptr都會指向同一塊記憶體(b在ctor中new出來的那一塊),這將造成沒有任何人指向a new出來的那塊記憶體,而這塊記憶體將既無法回收也不能再被程式的其他部分所用,memory leak因此而產生。
因為compiler隱式產生的assignment operator和copy ctor,在該class需要管理資源時有以上這些問題,我們才需要自行撰寫copy ctor和assignment operator:

CFoo(const CFoo& other)
{
    if (other.m_ptr) {
        m_ptr = new int (*other.m_ptr);
    } else {
        m_ptr = NULL;
    }
}

CFoo& operator=(const CFoo& other)
{
    if (this != &other)
    {
        delete m_ptr;
        if (other.m_ptr) {
            m_ptr = new int(*other.m_ptr);
        } else {
            m_ptr = NULL;
        }
    }

    return *this;
}

Leave a comment