1. 程式人生 > >Effective C++(12) 複製物件時要複製每一個成員

Effective C++(12) 複製物件時要複製每一個成員

問題聚焦:
負責拷貝的兩個操作:拷貝建構函式和過載賦值操作符。 一句話總結,確保被拷貝物件的所有成員變數都做一份拷貝。
Demo
void logCall(const std::string& funcName);  // log函式

class Date{ ... };

class Customer {
public:
    ....
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
private:
    std::string name;
    Date lastTransaction;
};

Customer::Customer(const Customer& rhs) 
    : name(rhs.name)      // 忽略了lastTransaction成員變數
{
    logCall("Customer copy constructor");
}

Customer& Customer::operato=(const Customer& rhs)
{
    logCall("Customer copy assignment operator");
    name = rhs.name;         // 忽略了lastTransaction成員變數
    return *this;
}
上述程式碼的問題很明顯,就是沒有拷貝lastTransaction成員變數,更為嚴重的是,編譯器並不會提醒你這個錯誤。 結論就是,如果為class新增一個成員變數,必須同時修改copying函式(包括拷貝建構函式和過載賦值操作符) 如果發生了繼承,會帶來什麼樣的危險呢?
class PriorityCustomer: public Custoer {
public:
    ...
    PriorityCustomer(const PriorityCustoer& rhs);
    PriorityCustomer& operato=(const PriorityCustomer& rhs);
    ...
private:
    int priority;
};

PriorityCustomer&
PriorityCustomer::PriorityCustomer (const PriorityCustomer& rhs)
    : priority(rhs.priority)
{
    logCall("PriorityCustoer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    logCall("PriorityCustomer copy assignment operator");
    priority = rhs.priority;
    return *this;
}

問題:
PriorityCustomer的拷貝函式們拷貝了它宣告的成員變數,卻忽略了它所繼承的Customer成員變數的拷貝,也沒有指定實參傳遞給Customer的建構函式,因此,PriorityCustomer物件的Customer成分會被不帶實參的Customer預設建構函式初始化。

改進:為子類編寫拷貝函式時,必須要複製其基類部分。
PriorityCustomer&
PriorityCustomer::PriorityCustomer (const PriorityCustomer& rhs)
    : Customer(rsh), priority(rhs.priority)
{
    logCall("PriorityCustoer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    logCall("PriorityCustomer copy assignment operator");
    Customer::operator=(rhs);
    priority = rhs.priority;
    return *this;
}

結論
確保賦值所有local成員變數
呼叫所有base classes內的適當的拷貝函式

需要注意的一點是:
雖然過載賦值操作符和拷貝函式的程式碼長得很像,但是很難複用它們,因為過載賦值操作符時呼叫拷貝建構函式就像在構造一個已經存在的物件; 同樣地,令拷貝建構函式呼叫過載賦值操作符一樣沒有意義。
所以,複用這兩個程式碼的方式就是寫一個新的成員函式給兩者呼叫。 小結:
拷貝建構函式和過載賦值操作符應該確保複製“物件內的所有成員變數”及“所有base class成分”; 不要嘗試拷貝建構函式呼叫過載賦值操作符或是反過來。
參考資料: 《Effective C++ 3rd》