29.避免返回內部數據的句柄。
即使聲明一個類的對象為const,不能進行修改,在獲得其數據的句柄也就是地址的情況下,還是可以強行修改的。
class A{ public: int n; A(int x):n(x){} operator int*() const; }; inline A::operator int*()const{ return const_cast(&n); } int main(){ const A a(1); A& b = const_cast(a); b.n = 2; cout<
使用const_cast, 再給常對象起一個別名就可以修改這個 本來不能修改的常量對象了。這種方法是無法避免的,常對象注定只是表面上的,最終還是可以修改的,因為人們可以通過各種方式獲得其內存,然後強制修改內存上的內容。
不考慮這種殘忍的方法,在重載int* 操作符時,由於函數體是const,n就成了 const int n,所以&n獲得的是一個const int*,而返回值應該是一個int*,不是常量指針,所以又使用了const_cast,返回了一個指針,這個情況不科學,換另外一種情況A內保存了一個指針 data,指向儲存的數據,對於const,表示指針是常量不能改變,但指針指向的內容可以改變,即 data為 int * const data ,則data可以作為int * 當作返回值傳遞給調用者,而調用者即可以隨意修改data指向的私有數據。
則對於const對象的的 T * 操作符,如果選擇申請一個新的內存來存放數據,並返回這塊新數據的指針的話,速度較慢,且內存容易洩漏。而最好的方法應該是使這個指向const對象返回的指針為const指針,這樣即安全有快速:
inline A::operator int *const() const{ return data; }
指針並不是返回內部數據句柄的唯一途徑,引用也會。解決方法一樣,對於const對象就應該返回const的引用,對與普通對象才返回普通的引用。
而並不是只有const成員函數需要擔心返回句柄的問題,對於非const成員,必須注意,句柄的合法性失效的時間與它所對應的對象完全相同。
30.避免這樣的成員函數:其返回值是指向成員的非const指針或引用,但成員的訪問級比這個函數低。
這是數據封裝所必須的,隨意返回被封裝的數據的句柄就給了類外部隨意修改對象的數據成員的能力,這樣是不應該的。
但這種 錯誤很常見,因為程序員喜歡用引用來傳遞 來提高效率。傳遞指針也是如此。
類中的成員函數指針:
typedef void (A::*AmFun)();//AmFun為一個指向A中無參無返回值的函數指針這裡不能使用 typedef void (* fun)() 來指向類A中這個無參無返回值的函數,因為兩者類型是不同的,成員函數指針的類型是要有類名稱作為前綴的。也要避免使用成員函數指針將類內封裝的函數傳遞出去。
31.千萬不要返回局部對象的引用,也不要返回函數內部new初始化的指針的引用。
局部對象在離開其作用域後就會被系統銷毀,而new初始化的指針 是堆中內存,要麼在函數的最後內存被釋放了,要麼沒有釋放導致內存洩漏。這是比較簡單易懂的道理。
前者容易理解,對於後者,有些人說那我們每次調用函數後都規定必須釋放這些內存呗,但這顯然是不正確的,如果 operator + 返回的是new出內存的指針,對於只進行一次的操作很容易記住要delete指針,但對於多次操作: a+b+c+d+e+f+f 呢,要如何記錄並釋放這些內存?
32.盡可能的推遲變量的定義。
盡管c中要求將所有的聲明都放在前面。但c++中不這麼做,目的是減少消耗,定義變量就要調用其構造函數和析構函數,如果這個變量最終未用到,就會造成資源的浪費。
盡可能的減少消耗,如使用復制構造函數,而不是先缺省構造,再賦值。
33.明智的使用內聯。
函數有壓棧出棧的消耗,宏不安全可靠,而內聯函數就非常好。內聯函數的優點不只如此。為了處理那些沒有函數調用的代碼,編譯器優化程序進行了專門的設計,也會對內聯函數進行一定的優化。
內聯函數的代價。增加整個目標代碼的體積,程序體積大,計算機內存有限的話,即使有虛擬內存,但程序運行時還是會浪費許多時間在頁面調度中,即引發抖動,過多的內聯還會降低指令高速緩存的命中率。
內聯 inline指令是對編譯器的提示,如register一樣,事實上大多數編譯器會拒絕內聯復雜的函數,如包含循環和遞歸的函數。而且即使最簡單的虛函數,編譯器也無法內聯,其還是會將其放在內存中,並使用虛表中指針指向虛函數。
內聯函數一般都放在頭文件中,被外聯的內聯函數會造成的一種錯誤:如果兩個cpp共享同一個頭文件,這個頭文件中有 inline fun(),如果內聯正常,則一切正常,內聯代碼直接插入在調用的地方,如果內聯失敗,則在對應cpp中要加載並定義fun函數,而導致兩個cpp中包含兩個同樣的fun函數,當兩個目標文件鏈接時,編譯器會為程序中有兩個fun函數而報錯。舊標准中的解決方法是對於未內聯的內聯函數,編譯器會把它當作聲明為static的函數處理,但這樣在每個文件中都產生開銷。
當程序中要獲得一個內聯函數的地址時,編譯器還要為此生成一個函數體,在舊的規則中每個取內聯函數地址的被編譯單元會各自生成內聯函數的靜態拷貝,而新規則下,只會生成一個唯一的內聯函數的外部拷貝。
編譯器有時會生成構造函數和析構函數的外部拷貝,通過獲得這些指針來方便的構造和析構類的對象數組。對於在類內定義的函數都是內聯函數。但是對於一個類的構造函數,其並不是只有你所編寫的構造函數中的內容,它在編譯時會被加入一些代碼,如檢測是在堆中還是棧中創建對象,對類中成員進行初始化,調用基類的構造函數對基類進行初始化等。這樣會使構造函數實際的內容比你想象中還要多,導致無法內聯,析構函數也一樣。
程序員必須預先估計聲明內聯函數帶來的方面影響。如一個程序庫中聲明一個內聯函數,而對這個內聯函數進行升級時,要將所有使用該程序庫的用戶程序重新編譯,而如果這個函數不是內聯函數,只要重新鏈接即可。而如果這個函數的程序庫是動態鏈接的,程序庫的修改對用戶來說完全是透明的。
內聯函數中的靜態對象常常出現違違反直覺的行為,如果函數中包含靜態對象,要避免將它聲明為內聯函數。
大多數調試器遇上內聯函數無能為力,無法在一個不存在的函數裡設置斷點。
慎重的使用內聯。