標籤:解決 初始化順序 淺拷貝 構造 不可 拷貝建構函式 依據 檢查 賦值
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