【轉(zhuǎn)】http://www.shnenglu.com/tiandejian/archive/2007/08/19/EC_26.html
第五章. 實現(xiàn)
在大多數(shù)情況下,恰當?shù)刈龊妙悾ㄒ约邦惸0澹┑亩x和函數(shù)(以及函數(shù)模板)的聲明是整個實現(xiàn)工作的重中之重。一旦你順利地完成了這些工作,那么相關(guān)的實現(xiàn)工作大都是直截了當?shù)摹H欢@里還存在一些需要關(guān)注的事情:過早地定義變量可能會犧牲性能。濫用轉(zhuǎn)型可能會使代碼變得笨重且不以維護,同時也會困擾于無盡難于發(fā)現(xiàn)的 bug 。返回一個對象內(nèi)部內(nèi)容的句柄會破壞代碼的封裝性,同時留給客戶端程序員一個懸而未決的“野句柄”。如果對異常的影響考慮不周,那么將會帶來資源泄露和數(shù)據(jù)結(jié)構(gòu)的破壞。過分熱衷于使用內(nèi)聯(lián)會使代碼不斷膨脹。代碼文件過多過復(fù)雜會造成的過于復(fù)雜的耦合,程序的構(gòu)建時間會漫長得讓人無法忍受。
所有這些問題都是可以避免的。本章就來講解如何去做。
第26條: 定義變量的時機越晚越好
你經(jīng)常要使用構(gòu)造函數(shù)或者析構(gòu)函數(shù)來定義某個類型的一個變量,當系統(tǒng)控制在接收到這一變量的定義時,就引入了一次構(gòu)造過程的開銷;在變量達到自身作用域以外時,就引入一次析構(gòu)過程的開銷。未使用的變量也會帶來一定的開銷,所以你應(yīng)該盡可能的避免這種浪費的出現(xiàn)。
你可能會想你永遠也不會定義變量而不去使用,但是你可能需要三思而后行。請觀察下邊的函數(shù),它在所提供的密碼足夠長時,可以返回一個加密版本的密碼。如果密碼長度過短,函數(shù)就會拋出一 個 logic_error 類 型的異常(這個異常類型定義于標準 C++ 庫中,參見第 54 條):
// 這個函數(shù)定義 "encrypted" 變量的時機過早
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... // 對密碼加密
return encrypted;
}
本函數(shù)中,對象 encrypted 并不是完全未使用的,但是在拋出異常的情況下,函數(shù)就不會使用它。也就是說,即使 encryptPassword 拋出一個異常的話,你也要為 encrypted 付出一次構(gòu)造和一次析構(gòu)的代價。因此,你最好這樣做:推遲 encrypted 的定義,直到你確認你需要它時再進行 :
// 這個函數(shù)推遲了 encrypted 的定義,直到真正需要它時再進行
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
... // 對密碼加密
return encrypted;
}
上面的代碼還不像你想象的那么嚴謹,這是因為在定義 encrypted 是沒有為它設(shè)置任何初始化參數(shù)。這就意味著編譯器將調(diào)用它的默認構(gòu)造函數(shù)。通常情況下,你要對一個對象需要做的第一件事就是為它賦一個值,通常是通過一次賦值操作。第 4 條中解釋了為什么使用默認構(gòu)造函數(shù)構(gòu)造對象并為其賦值,要比使用需要的值對其進行初始化的效率低一些。那里的分析符合此處的情況。比如說,可以假設(shè)的較困難的部分是通過下面的函數(shù)來解決的:
void encrypt(std::string& s); // encrypts s in place
encryptPassword 就應(yīng)該以下面的方式來實現(xiàn)了,盡管它不是最優(yōu)秀的:
// 這一函數(shù)推遲了 enctypted 定義的時機,直到需要時才進行。
// 但仍然會帶來不必要的效率問題。
std::string encryptPassword(const std::string& password)
{
... // 同上,檢查密碼長度
std::string encrypted; // encrypted 的默認構(gòu)造函數(shù)版本
encrypted = password; // 對 encrypted 賦值
encrypt(encrypted);
return encrypted;
}
更好的一種實現(xiàn)方式是,使用 password 來初始化 encrypted ,這樣就可以跳過默認構(gòu)造過程所帶來的無謂的性能開銷:
// 最后給出定義和初始化 encrypted 的最佳方法
std::string encryptPassword(const std::string& password)
{
... // 檢查長度
std::string encrypted(password);// 通過拷貝構(gòu)造函數(shù)定義和初始化
encrypt(encrypted);
return encrypted;
}
此時標題中 的“越 晚 越好”的含義就十分明顯了。你不僅僅要推遲一個變量的定義時機,直到需要它時再進行;你還需要繼續(xù)推遲,直至你掌握了它的初始化參數(shù)為止。這樣做,你就可以避免去構(gòu)造和析構(gòu)不必要的對象,你也可以避免那些無關(guān)緊要的默認構(gòu)造過程。還有,通過初始化這些變量,定義這些變量的目的一目了然,從而代碼也變得更加清晰。
“但是循環(huán)呢?”你可 能會想。如果一個變量僅僅在循環(huán)題中使用,那么更好的選擇是:將它定義在循環(huán)題的外部,在每次循環(huán)迭代前對其進行賦值;還是:在循環(huán)體的內(nèi)部定義變量?也就是說,哪種基本結(jié)構(gòu)是更優(yōu)秀的呢?
// 方法 A :在循環(huán)體外部定義
Widget w;
for (int i = 0; i < n; ++i){
w = 取決于 i 的某個值 ;
...
}
// 方法 B: 在循環(huán)體內(nèi)部定義
for (int i = 0; i < n; ++i) {
Widget w( 取決于 i 的某個值 ) ;
...
}
這里我使用了 Widget 類型的對象,而不是 string 類型的對象,從而避免了進行構(gòu)造、析構(gòu)、或者對象賦值等過程帶來的誤差。
對于 Widget 的操作而言,上面兩種方法所帶來的開銷如下:
l 方法 A : 1 個構(gòu)造函數(shù) + 1 個析構(gòu)函數(shù) + n 次賦值。
l 方法 B : n 個構(gòu)造函數(shù) + n 個析構(gòu)函數(shù)。
對于那些一次賦值操作比一對構(gòu)造 - 析構(gòu)操作開銷更低的類而言,方法 A 是較高效的。尤其是在 n 較大的情況下。否則方法 B 就是更好的選擇。還有,方法 A 使得 w 位于一個比方法 B 更大的作用域中,這是違背程序的可讀性和可維護性原則的。因此,除非你確認 : (1) 賦值操作比一對構(gòu)造 - 析構(gòu)操作更高效, (2) 當前代碼是對性能敏感的;其他任何情況下,你都應(yīng)該使用方法 B 。
銘記在心
l 定義變量的時機越晚越好。這可以提高程序的清晰度和工作效率。