複製建構子 Copy Constructor

建構子 Constructor 那篇文章中,我們介紹了什麼是建構子,和類別的關係是什麼。但是建構子的深度遠遠不止於此。

這章我們要來介紹更為進階的觀念:複製建構子 Copy Constructor!

什麼是複製建構子

我們知道建構子是類別用來建立實體物件的函數。在一般的建構子,我們是透過傳進類別所需要的參數進行建構。

比如說:

Object obj = Object(3, 4);

我們利用傳入參數 34 來建構出 Object 這個物件。

而複製建構子,就是讓我們利用已經存在的物件去建構出新的物件。

比如說:

Object obj = Object(3, 4);
Object new_obj = Object(obj);

我們就利用了已存在的物件 obj 去建立了一個新的物件 new_obj

如何創建

複製建構子的建立除了和建構子的建立擁有相同的規則以外,還多了第三條規則:

  1. 名稱必須和類別名稱相同
  2. 沒有回傳值
  3. 參數必須是同一個類別型態,並且是 pass by reference

注意這第三個規則非常重要,我們還沒有講過什麼是 pass by reference。這邊我們先不講,但基本上這個規定和程式碼運行的效率有直接關係。

如果想知道什麼是 pass by reference 可以參考 IBM 這篇 Pass by reference (C++ only)

我們來看看複製建構子會長怎麼樣:

class Object
{
    Object(Object& obj) {}
};

有沒有發現其實和建構子長的幾乎一模一樣,只是在參數中我們加入了 Object& obj

實際範例

了解了複製建構子的意義以及如何創建後,我們繼續利用 Object 當我們的範例:

class Object
{
private:
    int m_x;
    int m_y;

public:
    Object(int x, int y) : m_x(x), m_y(y)
    {
        std::cout << "呼叫建構子:Object(" << x << ", " << y << ")" << std::endl;
    }

    Object(Object& obj) : m_x(obj.m_x), m_y(obj.m_y)
    {
        std::cout << "呼叫複製建構子:Object(Object& obj)" << std::endl;
    }        

    void print() const
    {
        std::cout << "Object(" << m_x << ", " << m_y << ")" << std::endl;
    }
};

int main()
{
    Object obj = Object(3, 4);
    Object new_obj = Object(obj);

    new_obj.print();
    return 0;
}

在複製建構子的 member initializer list 中,我們可以看到,我們將傳進來的物件的特徵值 xy 複製到新物件的特徵中。

這讓新物件與原物件擁有相同的特徵值,這樣知道為什麼這個叫做複製建構子了吧!

我們可以在輸出結果中看到在建立原物件 obj 時,呼叫的還是一般的建構子。

但是在建立新物件 new_obj 時,被呼叫的確實是複製建構子!

呼叫建構子:Object(3, 4)
呼叫複製建構子:Object(Object& obj)
Object(3, 4)

隱藏的複製建構子

其實,如果我們不將複製建構子寫出來,上面的程式碼一樣可以執行。

這是因為 C++ 會幫我們寫一個「隱藏版」的複製建構子。程式碼在執行時,若沒有看到使用者寫的複製建構子,C++ 就會將隱藏版的複製建構子當作預設。

反之,如果使用者有寫複製建構子,比如說上面的程式碼,那麼 C++ 就會用使用者寫的。

這個隱藏版的基本上就是將特徵值複製給新的物件而已,大概長這樣:

Object(Object& obj) : m_x(obj.m_x), m_y(obj.m_y)
{
}

另外,我們可以利用 defaultdelete 這兩個關鍵字來強制使用預設的複製建構子或刪除複製建構子。

比如說:

// 強制使用預設複製建構子
Object(Object& obj) = default;

// 刪除複製建構子
Object(Object& obj) = delete;

如果我們刪除了複製建構子,再執行上面的程式碼時,我們會發現程式會報類似這樣的錯誤:

error: use of deleted function 'Object::Object(Object&)'

至於我們為什麼會需要刪除複製建構子呢?

雖然這樣的情況不多,但一個情況是當預設複製建構子的行為會違反其他物件的規則時,就必須刪除複製建構子。

有興趣可以看 Stack Overflow 這篇 Why and when delete copy constructor,但這也牽扯到了 Shallow Copy and Deep Copy 的觀念。我會說這個觀念對想要更深刻理解 C++ 其實滿重要的,以後有機會我會再專門寫一篇講解!

總結

這章我們了解了什麼是複製建構子 Copy Constructor,它的作用以及該如何運作。

還提到了一些更進階的概念,但還沒有機會深入說明,以後會再專門給那些概念寫獨立文章的!

那就希望這篇有讓你們學到東西囉~