程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++讀書筆記(16)

Effective C++讀書筆記(16)

編輯:C++入門知識

第15篇 http://www.BkJia.com/kf/201202/118370.html

條款26:盡可能延後變量定義式出現的時間
 
Postpone variable definitions as long as possible
 
只要你定義了一個帶有構造函數和析構函數的類型變量,當控制流程到達變量定義時,你會承受構造成本,而當變量離開作用域時,你會承受析構成本。如果有最終並未被使用的變量造成這一成本,你就要盡你所能去避免它。
 
不要認為自己不會定義一個不使用的變量。考慮下面這個函數,它計算通行密碼的加密版本然後返回,前提是密碼夠長。如果密碼太短,函數就會拋出一個定義在C++標准程序庫中的logic_error類型異常:
 
// 這個函數過早定義變量encrypted
std::string encryptPassword(const std::string& password)
{
    using namespace std;
    string encrypted;
    if (password.length() <MinimumPasswordLength) {
        throw logic_error("Passwordis too short");
    }
    ... // 必要動作,將一個加密後的密碼置入變量encrypted
    return encrypted;
}
 
如果拋出了一個異常,對象encrypted在這個函數中就是無用的。換句話說,即使encryptPassword拋出一個異常,你也要為構造和析構encrypted付出代價。因此你最好將encrypted的定義推遲到你確信你真的需要它的時候,即判斷是否會拋出異常之後。
 
這還不夠,因為定義encrypted的時候沒有任何初始化參數。這就意味著很多情況下將使用它的缺省構造函數。缺省構造一個對象然後賦值比用你真正需要它持有的值初始化它效率更低。例如,假設encryptPassword的核心部分是在這個函數中完成的:
 
voidencrypt(std::string& s); // 在其中的適當地點對s加密
 
那麼,encryptPassword可實現如下,即使它還不是最好的方法:
 
std::stringencryptPassword(const std::string& password)
{
    ... // 檢查length,如前
    std::string encrypted; // default-construct encrypted
    encrypted = password; // 賦值給encrypted
    encrypt(encrypted);
    return encrypted;
}
 
更可取的方法是用password初始化encrypted,從而跳過毫無意義並可能很昂貴的缺省構造:
 
std::stringencrypted(password); // 通過copy構造函數定義並初始化
 
這就是本條款的標題的真正含義。你不僅應該推遲一個變量的定義直到你不得不用它的最後一刻,而且應該試圖推遲它的定義直到得到了它的初始化參數。通過這樣的做法,你可以避免構造和析構無用對象,而且還可以避免不必要的缺省構造。
 
 
 
對於循環,如果一個變量僅僅在一個循環內使用,是循環外面定義它並在每次循環迭代時賦值給它更好,還是在循環內部定義這個變量更好?
 
 //Approach A: 定義於循環外
Widget w;
for (int i = 0; i < n; ++i){
    w = 取決於i的某個值;
    ...
}
 
// Approach B: 定義於循環內
for (int i = 0; i < n; ++i) {
    Widget w(取決於i的某個值);
    ...
}
 
對於Widget的操作而言,就是下面這兩個方法的成本:
 
·    方法A:1個構造函數+ 1個析構函數+ n個賦值。
 
·    方法B:n個構造函數+ n個析構函數。
 
對於那些賦值的成本低於一個構造函數/析構函數對的成本的類,方法A通常更高效,特別是在n變得很大的情況下。否則,方法B可能更好一些。此外,方法A與方法B相比,使得名字w 在一個較大的區域(包含循環的那個區域)內均可見,這可能會破壞程序的易理解性和可維護性。因此得出結論,除非你確信以下兩點:(1)賦值比構造函數/析構函數對成本更低,(2)你正在涉及你代碼中性能敏感的部分,否則你應該默認使用方法B。
 
·    盡可能延後變量定義式的出現。這樣可以增加程序的清晰度並提高程序的性能。
 
 
 
條款27:盡量少做轉型動作(1)
 
Minimize casting
 
強制轉型破壞了類型系統。它會引起各種各樣的麻煩,其中一些容易被察覺,另一些則格外地隱晦。強制轉型在C,C#和Java中比在C++中更有必要,危險也更少。在C++語言中,強制轉型是一個必須全神貫注的特性。
 
C風格(C-style)強制轉型如下:
 
(T) expression // 將expression轉型為T
 
函數風格(Function-style)強制轉型使用這樣的語法:
 
T(expression) // 將expression轉型為T
 
這兩種形式之間沒有本質不同,純粹就是一個把括號放在哪的問題。這兩種形式被稱為舊風格(old-style)的強制轉型。
 
C++ 同時提供了四種新的強制轉型形式(通常稱為新風格的或C++風格的強制轉型):
 
(因對強制轉換不熟悉,且書上的解釋自覺不夠清晰,以下內容多摘自網絡)
 
1)static_cast < type-id > ( expression )
 
該運算符把expression轉換為type-id類型,但沒有運行時類型檢查來保證轉換的安全性。編譯器隱式執行任何類型轉換都可由static_cast顯示完成。它主要有如下幾種用法:
 
  ①用於類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換:
 
  進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
 
  進行下行轉換(把基類指針或引用轉換成派生類表示)時,由於沒有動態類型檢查,所以是不安全的。
 
  ②用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
 
  ③把空指針轉換成目標類型的空指針。
 
  ④把任何類型的表達式轉換成void類型。
 
注意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性。
 
 
 
2)dynamic_cast < type-id > ( expression )
 
  該運算符把expression轉換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void*;如果type-id是類指針類型,那麼expression也必須是一個指針,如果type-id是一個引用,那麼expression也必須是一個引用。
 
dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
 
在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
 
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
 
 
 
3)const_cast < type_id > ( expression )
 
  該運算符用來修改類型的const或volatile屬性。除了const或volatile修飾之外,type_id和expression的類型是一樣的。
 
  ①常量指針被轉化成非常量指針,並且仍然指向原來的對象;
 
  ②常量引用被轉換成非常量引用,並且仍然指向原來的對象;
 
  ③常量對象被轉換成非常量對象。
 
Volatile和const類似。
 
Volatile:同const、static一樣,這是一個類型修飾符。一個使用volatile修飾的變量,比如volatileint i; 每次對該變量的直接引用,都會訪問內存,而不是從寄存器中讀取(如果其已經在寄存器中)。這樣一來,volatile似乎沒什麼用處,反倒會使數據的讀取相對變慢很多,如果沒有volatile,編譯器可能會優化你的程序,使得數據從寄存器中讀取,從而加快程序的運行。但如果這個變量是同其它進程/線程共享的,就可能造成數據的不一致。多線程情況下,你可以使用互斥機制來保證對共享數據訪問的原子性。但是,在單片機等嵌入式環境中,硬件經常不會有這種互斥機制的支持,這時某些共享的數據(比如端口)就可能會產生不一致的情況。而使用volatile就會使編譯器不對代碼進行優化,每次對該變量的訪問都會從內存中讀取。
 
4)reinterpret_cast < type_id > ( expression ) 
 
  該操作符修改了操作數類型,但僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。例如:
 
    int *n= new int;
 
  double*d=reinterpret_cast<double*> (n);
 
  在進行計算以後, d 包含無用值. 這是因為reinterpret_cast僅僅是復制n比特位到d, 沒有進行必要的分析。
 
這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險的。因此,需要謹慎使用reinterpret_cast.
 
 
 
舊風格的強制轉型依然合法,但是新的形式更可取。首先,在代碼中它們更容易識別,簡化了在代碼中尋找類型系統被破壞的地方的過程。第二,更精確地指定每一個強制轉型的目的,使得編譯器診斷使用錯誤成為可能。例如,如果你試圖將常量性去掉,除非使用新式轉換的const_cast,否則無法通過編譯。
 
唯一使用舊式轉換的時機是當我要調用一個explicit構造函數傳遞一個對象給一個函數的時候,例如:
 
class Widget {
public:
explicit Widget(int size);
...
};
 
void doSomeWork(const Widget& w);
 
doSomeWork(Widget(15)); // 以int加上函數風格的轉型動作創建Widget
 
doSomeWork(static_cast<Widget>(15)); //以int加上C++風格的轉型動作創建Widget
 

摘自 pandawuwyj的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved