第十篇:複製物件時切記複製每一個成分

穆晨發表於2017-01-27

前言

       標題一看似乎是非常直觀簡單,沒必要特別寫下一篇隨便記錄。

       然而,在實際開發中,你會發現做到這一點對於經驗不足的 C++ 程式設計師來說絕對是一個挑戰。

       要做到複製每一個成分,就一定小心下面說到的兩點。

第一點

       類中新增了成員變數之後,需要在自定義的建構函式,解構函式,賦值運算子過載函式,拷貝建構函式中做出相應調整。

       請看下面一個例子:

 1 // 記錄呼叫資訊
 2 void logCall(const std::string & funcName);    
 3 
 4 // 顧客類
 5 class Customer {
 6 public:
 7     //......
 8     Customer(const Customer & rhs);    // 自定義拷貝建構函式
 9     Customer & operator=(const Customer & rhs);    // 自定義賦值運算子
10     //......
11 private:
12     std::string name;
13 };
14 
15 Customer::Customer(const Customer & rhs)
16     : name(rhs.name) 
17 {
18     logCall("Customer copy constructor");
19 }
20 
21 Customer & Customer::operator=(const Customer &rhs) {
22     logCall("Customer copy assignment operator");
23     name = rhs.name;
24     return *this;
25 }

       這段程式碼定義了一個顧客類,每次呼叫它的拷貝建構函式以及賦值運算子過載函式就會記錄下這次操作。

       這裡看起來一切都很美好,也確實是這樣的。

       但當你往這個類中新增了一個成員變數,不小心的話就會出問題了:

 1 class Data { /*......*/ };
 2 // 顧客類
 3 class Customer {
 4 public:
 5     //......
 6     Customer(const Customer & rhs);    // 自定義拷貝建構函式
 7     Customer & operator=(const Customer & rhs);    // 自定義賦值運算子
 8     //......
 9 private:
10     std::string name;
11     Data lastTransaction;        // 看這裡! 定義了一個新的變數
12 
13 };
14 
15 Customer::Customer(const Customer & rhs)
16     : name(rhs.name) 
17 {
18     logCall("Customer copy constructor");
19 }
20 
21 Customer & Customer::operator=(const Customer &rhs) {
22     logCall("Customer copy assignment operator");
23     name = rhs.name;
24     return *this;
25 }

       這裡,你必須要對自定義的建構函式,拷貝建構函式,賦值運算子過載函式都做出相應的修改才行。

第二點

       子類物件的自定義賦值運算子過載函式,拷貝建構函式要寫入父類成員拷貝的相關語句。

       還是以上面的例項來進行分析,這裡我設計了一個 PriorityCustomer 類繼承 Customer 類,如果要自定義它的拷貝建構函式和賦值運算子,請問這樣做對嗎?

 1 class PriorityCustomer : public Customer {
 2 public:
 3     //......
 4     PriorityCustomer(const PriorityCustomer & rhs);    // 自定義拷貝建構函式
 5     PriorityCustomer & operator=(const PriorityCustomer & rhs);    // 自定義賦值運算子
 6     //......
 7 private:
 8     int priority;
 9 };
10 
11 PriorityCustomer::PriorityCustomer(const PriorityCustomer & rhs)
12     : priority(rhs.priority) 
13 {
14     logCall("PriorityCustomer copy constructor");
15 }
16 
17 PriorityCustomer & PriorityCustomer::operator=(const PriorityCustomer &rhs) {
18     logCall("PriorityCustomer copy assignment operator");
19     priority = rhs.priority;
20     return *this;
21 }

       答案是否定的。因為這兩個函式中,只拷貝了子類物件新定義成員,它們繼承自父類部分卻沒有拷貝。

小結

       再說一點就是,如果你發現自定義的拷貝建構函式和賦值運算子過載函式有程式碼重複,那麼最好的做法是建立一個新的函式讓它們來呼叫;不要讓拷貝建構函式和賦值運算子過載函式相互呼叫,這是行不通的。

相關文章