11.為需要動態分配內存的類聲明一個拷貝構造函數和一個賦值操作符。
顯然,由于動態內存分配,絕對會有深淺拷貝的問題,要重寫拷貝構造函數,使其為深拷貝,才能實現真正意義上的拷貝。這是我理解的關于要聲明拷貝構造函數的原因。
而對于賦值操作符,類似的道理。
~~~
A b = a;
b = a;
~~~
對于上述兩種形式,上面調用的是復制構造函數,而下面才是 賦值操作符=。賦值與復制很相似,缺省的操作都是將類的全部成員進行復制。
深拷貝主要的操作很簡單,對于指針,動態申請一塊內存來存放指針指向的數據,每個指針都指向自己的一塊內存,而不是其他人的。
12.盡量使用初始化而不要在構造函數里賦值。
即盡量使用成員初始化列表,而不是使用賦值的方法。
首先對于const成員和引用,只能使用初始化列表來初始化。其次初始化列表效率更高,因為對象的創建分為兩步:數據成員的初始化和執行被調用構造函數體內的動作。即使用賦值之前,先進行了數據成員的初始化,然后才是賦值。所以使用初始化列表效率更高。
但當有大量的固定類型的數據成員要在每個構造函數中以相同的方式初始化的時候,使用賦值會更加合理一點。
13。初始化列表中成員列出的順序和它們在類中聲明的順序相同。
這是因為初始化列表的順序并不影響初始化的順序,初始化的順序是有成員在類中聲明的順序決定的,而讓其順序相同是使程序看起來是按照初始化列表的順序初始化。
而c++不使用初始化列表的順序的原因是:對象的析構函數是按照 與成員在構造函數中創建的相反的順序 創建的。則如果對象不是按照一種固定的順序來初始化,編譯器就要記錄下每一個對象成員的初始化順序,這將帶來較大的開銷。
14、確定基類有虛析構函數。
通過基類的指針去刪除派生類的對象時,基類一定要有虛析構函數,不然 會有不可預測的后果。不使用虛析構函數,只調用基類的析構函數去刪除派生類對象,這是無法做到,也是無法確定后果的。
構造函數調用是先基類后派生類,而析構函數的順序是先派生類后基類。
當一個類不作為基類使用時,使用虛析構函數是一個壞主意。因為虛函數的對象會有一個虛指針指向虛表,會浪費空間來儲存這個沒有意義的指針。
純虛函數在虛函數后加 =0 即可。
15.讓operator = 返回 *this 的引用。
= 號可以連接起來,因為其返回值的原因,聲明operator=的形式如下:
~~~
C& C:: operator= (const C&);
~~~
其輸入和返回都是類對象的引用。以實現連續的賦值操作。返回值是 = 左邊值的引用 即 *this的引用,因為右邊即參數是const類型的。
函數的參數是 const類型的原因,是 = 右邊的值經過計算會獲得一個新的結果,而這個對象要用一個臨時對象來儲存,這個臨時對象是const類型的,因為其作為函數的參數,且不能被函數修改。
16.在operator = 中對所有數據成員賦值。
對于深拷貝要自己寫一個更加正確的 = 操作。
在涉及繼承時,派生類的賦值運算必須處理基類的賦值。如果重寫派生類的賦值運算,就必須同時顯示的對基類部分進行賦值。
~~~
class A{
public:
int a;
A(int x):a(x){}
// A& operator=(const A& x){ a = x.a; return *this;}
};
class B:public A{
public:
int b;
B(int x):A(x),b(x){}
B& operator=(const B&);
};
B& B::operator=(const B& x){
if(this == &x)return *this;
// A::operator=(x);//調用基類的賦值函數要如此寫
static_cast<A&>(*this) = x;//也可以強制轉換為A類型然后在調用基類的默認賦值函數
b = x.b;
return *this;
}
~~~
拷貝構造函數也是如此,也要對基類的成員進行復制,只要在成員初始化列表中添加基類即可。
17.在operator=中檢查給自己賦值的情況。
這是基于效率考慮的,在賦值的首部檢測是給自己賦值,就立即返回,如16中函數所寫的那樣。這里除了類中自己成員的賦值,如果有基類,還要調用基類的賦值函數,會增加開銷。
另一個原因是保證正確性。一個賦值運算符必須首先釋放掉一個對象的資源,如有些指針指向了動態申請的空間,則賦值前一般要釋放這些資源,然后在指向新的資源(如果在賦值開始,用些臨時的指針來記錄之前的所有指針指向的內存,然后在賦值后再將臨時指針指向的內存全部釋放。這還是不行,因為對這些指針如p,必須有 p = new p[]...來指向新申請的一塊空間,而如果是同一個對象的話,這里就將原來的指針指向一個新的地址,且兩者相同了,所以又需要一個新的臨時指針來指向賦值對象的指針的值),也就是說為了使其給自己賦值,對與每個指針必須新建兩個指針,一個儲存左邊的對象的原指針,一個儲存右邊的對象的原指針,這樣的開銷時極大極浪費的,也是沒有必要的,為了一些不應該進行的為自己賦值要提前準備大量內存來儲存數據,這也是不科學的。
所以最好的解決辦法是檢測是否為自己賦值,一般采用檢測對象地址是否相等,c++中一般采取這種方法。對于java中,不好的做法是檢測對象是否相等,即其全部值是否完全相等,較好的方法是根據對象的id來判斷對象是否為同一個對象。