若寫下
class Empty { };
當C++處理過它之后,編譯器就會為它聲明一個default構造函數(shù),一個copy構造函數(shù),一個copy assignment操作符和一個析構函數(shù)。這就好像寫下這樣的代碼:
class Empty {
public:
Empty() { ... }
Empty(const Empty& rhs) { ... }
~Empty() { ... }
Empty& operator=(const Empty& rhs) { ... }
}
惟有這些函數(shù)被需要(被調用),它們才會被編譯器創(chuàng)建出來。
Empty e1; //default構造函數(shù)
Empty e2(e1); //copy構造函數(shù)
e2 = e1; //copy assignment操作符
default構造函數(shù)和析構函數(shù)主要是給編譯器一個地方來放置“藏身幕后”的代碼,像是調用base classes和non-static成員變量的構造函數(shù)和析構函數(shù)。注意,編譯器產(chǎn)出的析構函數(shù)是個non-virtual。copy構造函數(shù)和copy assignment操作符,編譯器創(chuàng)建的版本只是單純地將來源于對象的每一個non-static成員變量拷貝到目標對象。
templateclass NameObject {
public:
NameObject(const char* name, const T& value);
NameObject(const std::string name, const T& value);
private:
std::string nameValue;
T objectValue;
};
NameObjectno1("Smallest Prime Number", 2);
NameObjectno2(no1);
編譯器生成的copy構造函數(shù)必須以no1.nameValue和no1.objectValue為初值設定no2.nameValue和no2.objectValue。nameValue的類型是string,調用string的copy構造函數(shù)。objectValue的類型是int,是個內置類型,所以會拷貝每一個bits來完成初始化。
若NameObject的定義如下,其中的nameValue是個referencetostring,objectValue是個const T:
templateclass NameObject {
public:
NameObject(std::string& name, const T& value);
private:
std::string &nameValue; //是個reference
const T objectVaule; //是個const
};
std::string newDog("Persephone");
std::string oldDog("Satch");
NameObjectp(newDog, 2);
NameObjects(oldDog, 36);
p = s; // p的成員變量該發(fā)生什么事?
編譯器拒絕編譯賦值動作。
如果某個base classes將copy assignment操作符聲明成private,編譯器將拒絕為derived classes生成一個copy assignment操作符。
請記住:
編譯器可以暗自為class創(chuàng)建default構造函數(shù)、copy構造函數(shù)、copy assignment操作符,以及析構函數(shù)。
條款06:若不想使用編譯器自動生成的函數(shù),就該明確拒絕用一個class描述待售房屋
class HomeForSale { ... };
每個售賣的房屋都是獨一無二的,所以HomeForSale對象不能被復制。
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); //企圖拷貝h1,不應通過編譯
h1 = h2; //企圖拷貝h2,不應通過編譯
如果不聲明copy構造函數(shù)或copyassignment操作符,編譯器可能會為你產(chǎn)出一份,支持copying;
如果聲明了copy構造函數(shù)或copyassignment操作符,那就還是支持copying。
所有編譯器產(chǎn)出的函數(shù)都是public,我們可以把copy構造函數(shù)和copyassignment操作符聲明成private。但是成員函數(shù)和友元函數(shù)還是可以調用private函數(shù),解決辦法就是“將成員函數(shù)聲明為private而且故意不實現(xiàn)它們”。
class HomeForSale {
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale &);
};
請記?。?/p>
為駁回編譯器自動(暗自)提供的機能,可將相應的成員函數(shù)聲明為private并且不予實現(xiàn)。
條款07:為多態(tài)基類聲明virtual析構函數(shù)class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock : public TimeKeeper { ... };
class WaterClock : public TimeKeeper { ... };
class WristWatch : public TimeKeeper { ... };
// 使用工廠模式函數(shù),返回一個指向derived class對象的base class指針
TimerKeeper* ptk = getTimeKeeper();
...
delete ptk;
C++明確指出,當derived class對象經(jīng)由一個base class指針被刪除,而該base class帶著一個non-virtual析構函數(shù),其結果未有定義——實際執(zhí)行時通常發(fā)生的是對象的derived成分沒被銷毀。解決問題辦法很簡單,給base class一個virtual的析構函數(shù)。
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;
任何class只要帶有virtual函數(shù)都幾乎確定應該有個virtual析構函數(shù)。
如果class不含有virtual函數(shù),通常表示它并不意圖被用作一個base class。當class不企圖被當作base class,令其析構函數(shù)為virtual往往是個餿主意。
class Point {
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
欲實現(xiàn)出virtual函數(shù),對象必須攜帶某些信息,主要用來控制運行期決定哪一個virtual函數(shù)該被調用。這份信息通常是由一個所謂vptr(vitrual table pointer)指針指出。vptr指向一個由函數(shù)指針構成的數(shù)組,成為vtbl(vitrual table);每一個帶有virtual函數(shù)的class都有一個對應的vtbl。當對象調用某一virtual函數(shù),實際調用的函數(shù)取決于該對象的vptr所指的那個vtbl——編譯器在其中尋找適當?shù)暮瘮?shù)指針。
若Point class內含virtual函數(shù),其對象的體積會增加。
std::string,STL中容器如vector,list,set等等都不帶有virtual析構函數(shù),不要繼承一個標準容器或其他“帶有non-virtual析構函數(shù)”的class。
純虛函數(shù)(pure virtual),擁有純虛函數(shù)的類是抽象類。
class AWON {
public:
virtual ~AWON() = 0;
};
析構函數(shù)的運作方式是,最深層派生(most derived)的那個class其析構函數(shù)最先被調用,然后是其每一個base class的析構函數(shù)被調用。?編譯器會在AWON的derived classes的析構函數(shù)中創(chuàng)建一個對~AWON的調用動作,所以必須為這個函數(shù)提供一份定義,否則鏈接器會發(fā)出抱怨。
請記住:
帶有多態(tài)性質的base classes應該聲明一個virtual的析構函數(shù)。如果class帶有任何的virtual函數(shù),那么它就應該擁有一個virtual析構函數(shù)。
classes的設計目的如果不是作為base classe使用,或不是為了具備多態(tài)性,就不應該聲明virtual析構函數(shù)。
條款09:絕不在構造和析構過程中調用virtual函數(shù)class Transaction {
public:
Transaction();
virtual void logTransaction() const = 0;
...
};
Transaction::Transaction() {
...
logTransaction();
}
class BuyTransaction : public Transaction {
public:
virtual void logTransaction() const;
};
class SellTransaction : public Transaction {
public:
virtual void logTransaction() const;
};
BuyTransaction b; //會發(fā)生什么?
BuyTransaction的構造函數(shù)被調用,但首先Transaction構造函數(shù)一定會更早被調用;Transaction構造函數(shù)的最后一行調用了virtual函數(shù)logTransaction,調用的是Transaction內的版本,而不是BuyTransaction內的版本。base class的構造期間virtual函數(shù)是不會下降到derived class階層。同樣道理也適用于析構函數(shù)。
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo);
};
Transaction::Transaction(const std::string& logInfo) {
logTransaction(logInfo);
}
class BuyTransaction : public Transaction {
public:
BuyTransaction(parameters) : Transaction(createLogString(parameters)) {
}
private:
static std::string createLogString(parameters);
};
可以由derived classes將必要的信息向上傳遞給至base class的構造函數(shù)。BuyTransaction內的private static函數(shù)作用,此函數(shù)是static函數(shù),就不可能意外指向“初期未成熟之BuyTransaction對象內尚未初始化的成員變量”。?
請記?。?/p>
在構造和析構期間不要調用virtual函數(shù),因為這類調用從不下降到derived class。
條款10:令operator=返回一個referenceto*this關于賦值,可以寫成連鎖形式
int x,y,z;
x = y = z = 15;
賦值采用的是右結合,所以上述賦值被解析為:
x = (y = (z = 15));
15先賦值給z,然后其結果(更新后的z)再賦值給y,然后其結果(更新后的y)再賦值給x。
為了實現(xiàn)連續(xù)賦值,賦值操作符必須返回一個reference指向操作符的左側實參。
class Widget {
public:
Widget& operator=(const Widget& rhs) {
...
return *this;
}
Widget& operator+=(const Widget& rhs) {
...
return *this;
}
};
請記住:
令賦值操作符返回一個reference to*this。?
條款11:在operator=中處理“自我賦值”class Widget { ... }
Widget w;
w = w;
看起來有點愚蠢,但它合法。
a[i] = a[j]; //當i和j有相同值時
*px = *py; //當px和py指向相同的東西
class Bitmap { ... };
class Widget {
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs) {
delete pb; //刪除原來的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的一個副本
return *this;
}
增加“證同測試”
Widget& operator=(const Widget& rhs) {
if(this == &rhs) return *this;
...
}
當new Bitmap拋出異常時,pb保持原狀。即使沒有證同測試,這段代碼還是能夠處理自我賦值,雖然不是效率最高,但是它行的通。
Widget& Widget::operator=(const Widget& rhs) {
Bitmap* pOrg = pb; //記住原來的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的一個副本
delete pOrg; //刪除原來的pb
return *this;
}
一個常見而夠好的operator=撰寫方法如下:
class Widgt {
...
void swap(Widget& rhs); //交互*this和rhs的數(shù)據(jù)
...
};
Widget& Widget::operator=(const Widget& rhs) {
Widget temp(rhs);
swap(temp);
return *this;
}
另外一個變奏,是用以by value方式接受實參
Widget& Widget::operator=(Widget rhs) { //rhs是被傳遞對象的一個副本
swap(rhs); //將*this的數(shù)據(jù)和副本的數(shù)據(jù)交換
return *this;
}
請記?。?/p>
確保當對象自我賦值時operator=有良好的行為。其中技術包括比較“來源對象”和“目標對象”的地址、精心周到的語句順序、以及copy-and-swap。
確認任何函數(shù)如果操作一個以上的對象,而其中多個對象是同一個對象時,其行為仍然正確。
條款12:賦值對象時勿忘其每一個成分void logCall(const std::string& funcName);
class Customer {
public:
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
std::string name;
};
Customer::Customer(const Customer& rhs) : name(rhs.name) {
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs) {
logCall("Customer copy assignment operator");
name = rhs.name;
return *this;
}
當新增一個Date成員后:
class Date { ... };
class Customer {
public:
...
private:
std::string name;
Date lastTransaction;
};
這時既有的copying函數(shù)執(zhí)行的是局部拷貝:它們的確復制了name,但是沒有復制新增的lastTransaction。編譯器不會提醒你。如果為class增加了一個成員變量,必須同時修改copying函數(shù)。
一旦發(fā)生繼承,可能會造成一個潛藏危機。
class PriorityCustomer : public Customer {
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& operator=::PriorityCustomer(const PriorityCustomer& rhs) {
logCall("PriorityCustomer copy constructor");
priority = rhs.priority;
return *this;
}
PriorityCustomer的copying函數(shù)看起來好像是復制了PriorityCustomer內的每樣東西,再看一眼,它們復制了PriorityCustomer聲明的成員變量,對繼承自Customer的成員變量卻未被復制。
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) :
Customer(rhs), priority(rhs.priority) {
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=::PriorityCustomer(const PriorityCustomer& rhs) {
logCall("PriorityCustomer copy constructor");
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}
請記?。?/p>
Copying函數(shù)應用有確保復制“對象內的所有成員變量”及“所有base class成分”;
不要嘗試以某個copying函數(shù)調用另一個copying函數(shù)。應該將共同機能放進第三個函數(shù)中,并由兩個copying函數(shù)公用。
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
新聞名稱:EffectiveC++(2)----構造/析構/賦值運算-創(chuàng)新互聯(lián)
文章來源:http://aaarwkj.com/article6/ippig.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設計、企業(yè)網(wǎng)站制作、靜態(tài)網(wǎng)站、Google、網(wǎng)站內鏈、微信公眾號
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)