在設計良好的面向?qū)ο笙到y(tǒng)中,為了壓縮其對象內(nèi)部的空間,僅留兩個函數(shù)用于對象的拷貝:一般稱為拷貝構造函數(shù)(copy constructor)和拷貝賦值運算符(copy assignment operator)。我們將它們統(tǒng)稱為拷貝函數(shù)(copying functions)。如果需要,編譯器會生成拷貝函數(shù),而且闡明了編譯器生成的版本正象你所期望的:它們拷貝被拷貝對象的全部數(shù)據(jù)。
當你聲明了你自己的拷貝函數(shù),你就是在告訴編譯器你不喜歡缺省實現(xiàn)中的某些東西。編譯器對此好像怒發(fā)沖冠,而且它們會用一種古怪的方式報復:當你的實現(xiàn)存在一些幾乎可以確定錯誤時,它偏偏不告訴你。
考慮一個象征消費者(customers)的類,這里的拷貝函數(shù)是手寫的,以便將對它們的調(diào)用記入日志:
void logCall(const std::string& funcName); // make a log entry
class Customer {
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
std::string name;
};
Customer::Customer(const Customer& rhs)
: name(rhs.name) // copy rhs’s data
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
logCall("Customer copy assignment operator");
name = rhs.name; // copy rhs’s data
return *this; // see Item 10
}
這里的每一件事看起來都不錯,實際上也確實不錯——直到 Customer 中加入了另外的數(shù)據(jù)成員:
class Date { ... }; // for dates in time
class Customer {
public:
... // as before
private:
std::string name;
Date lastTransaction;
};
在這里,已有的拷貝函數(shù)只進行了部分拷貝:它們拷貝了 Customer 的 name,但沒有拷貝它的 lastTransaction。然而,大部分編譯器對此毫不在意,即使是在的警告級別(maximal warning level)。這是它們在對你寫自己的拷貝函數(shù)進行報復。你拒絕了它們寫的拷貝函數(shù),所以如果你的代碼是不完善的,他們也不告訴你。結論顯而易見:如果你為一個類增加了一個數(shù)據(jù)成員,你務必要做到更新拷貝函數(shù)。(你還需要更新類中的全部的構造函數(shù)以及任何非標準形式的 operator=。這個問題最為迷惑人的情形之一是它會通過繼承發(fā)生??紤]:
class PriorityCustomer: public Customer { // a derived class
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
...
private:
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
priority = rhs.priority;
return *this;
}
當你聲明了你自己的拷貝函數(shù),你就是在告訴編譯器你不喜歡缺省實現(xiàn)中的某些東西。編譯器對此好像怒發(fā)沖冠,而且它們會用一種古怪的方式報復:當你的實現(xiàn)存在一些幾乎可以確定錯誤時,它偏偏不告訴你。
考慮一個象征消費者(customers)的類,這里的拷貝函數(shù)是手寫的,以便將對它們的調(diào)用記入日志:
void logCall(const std::string& funcName); // make a log entry
class Customer {
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
std::string name;
};
Customer::Customer(const Customer& rhs)
: name(rhs.name) // copy rhs’s data
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
logCall("Customer copy assignment operator");
name = rhs.name; // copy rhs’s data
return *this; // see Item 10
}
這里的每一件事看起來都不錯,實際上也確實不錯——直到 Customer 中加入了另外的數(shù)據(jù)成員:
class Date { ... }; // for dates in time
class Customer {
public:
... // as before
private:
std::string name;
Date lastTransaction;
};
在這里,已有的拷貝函數(shù)只進行了部分拷貝:它們拷貝了 Customer 的 name,但沒有拷貝它的 lastTransaction。然而,大部分編譯器對此毫不在意,即使是在的警告級別(maximal warning level)。這是它們在對你寫自己的拷貝函數(shù)進行報復。你拒絕了它們寫的拷貝函數(shù),所以如果你的代碼是不完善的,他們也不告訴你。結論顯而易見:如果你為一個類增加了一個數(shù)據(jù)成員,你務必要做到更新拷貝函數(shù)。(你還需要更新類中的全部的構造函數(shù)以及任何非標準形式的 operator=。這個問題最為迷惑人的情形之一是它會通過繼承發(fā)生??紤]:
class PriorityCustomer: public Customer { // a derived class
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
...
private:
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
priority = rhs.priority;
return *this;
}