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