Effective C++ 11-17
11.為須要動態分配內存的類聲明一個拷貝構造函數和一個賦值操作符。
顯然,由於動態內存分配,絕對會有深淺拷貝的問題,要重寫拷貝構造函數。使其為深拷貝,才幹實現真正意義上的拷貝。這是我理解的關於要聲明拷貝構造函數的原因。
而對於賦值操作符,類似的道理。
A b = a; b = a;對於上述兩種形式,上面調用的是復制構造函數,而以下才是 賦值操作符=。
賦值與復制非常類似,缺省的操作都是將類的所有成員進行復制。
深拷貝基本的操作非常easy,對於指針。動態申請一塊內存來存放指針指向的數據,每一個指針都指向自己的一塊內存,而不是其它人的。
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來推斷對象是否為同一個對象。
Effective C++ 11-17