是的,我們同意C語言中變數要放在模組頭部定義的規定;但在C++中,還是取消這種做法吧,它沒必要,不自然,而且昂貴。
還記得嗎?如果定義了一個有建構函式和解構函式的類型的變數,當程式運行到變數定義之處時,必然面臨構造的開銷;當變數離開它的生命空間時,又要承擔析構的開銷。這意味著定義無用的變數必然伴隨著不必要的開銷,所以只要可能,就要避免這種情況發生。
正如我所知道的,你的編程方式優雅而不失老練。所以你可能會在想,你決不會定義一個無用的變數,所以本條款的建議不適用於你嚴謹緊湊的編程風格。但別急,看看下面這個函數:當口令夠長時,它返回口令的加密版本;當口令太短時,函數拋出logic_error類型的異常(logic_error類型在C++標準庫中定義,參見條款49):
// 此函數太早定義了變數"encrypted"
string encryptPassword(const string& password)
{
string encrypted;
if (password.length() < MINIMUM_PASSWORD_LENGTH) {
throw logic_error("Password is too short");
}
進行必要的操作,將口令的加密版本
放進encrypted之中;
return encrypted;
}
對象encrypted在函數中並非完全沒用,但如果有異常拋出時,就是無用的。但是,即使encryptPassword拋出異常(見條款M15),程式也要承擔encrypted構造和析構的開銷。所以,最好將encrypted延遲到確實需要它時才定義:
// 這個函數延遲了encrypted的定義,
// 直到真正需要時才定義
string encryptPassword(const string& password)
{
if (password.length() < MINIMUM_PASSWORD_LENGTH) {
throw logic_error("Password is too short");
}
string encrypted;
進行必要的操作,將口令的加密版本
放進encrypted之中;
return encrypted;
}
這段代碼還不是那麼嚴謹,因為encrypted定義時沒有帶任何初始化參數。這將導致它的預設建構函式被調用。大多數情況下,對一個對象首先做的一件事是給它一個什麼值,這通常用賦值來實現。條款12說明了為什麼"預設構造一個對象然後對它賦值"比"用真正想要的值來初始化這個對象"效率要低得多。這一論斷在此一樣適用。例如,假設encryptPassword中最難處理的部分在這個函數中進行:
void encrypt(string& s); // s在此加密
於是encryptPassword可以象這樣實現(當然,它不是最好的實現方式):
// 這個函數延遲了encrypted的定義,
// 直到需要時才定義,但還是很低效
string encryptPassword(const string& password)
{
... // 同上,檢查長度
string encrypted; // 預設構造encrypted
encrypted = password; // 給encrypted賦值
encrypt(encrypted);
return encrypted;
}
更好的方法是用password來初始化encrypted,從而繞過了對預設建構函式不必要的調用:
// 定義和初始化encrypted的最好方式
string encryptPassword(const string& password)
{
... // 檢查長度
string encrypted(password); // 通過拷貝建構函式定義並初始化
encrypt(encrypted);
return encrypted;
}
這段代碼闡述了本條款的標題中"儘可能"這三個字的真正含義。你不僅要將變數的定義延遲到必須使用它的時候,還要盡量延遲到可以為它提供一個初始化參數為止。這樣做,不僅可以避免對不必要的對象進行構造和析構,還可以避免無意義的對預設建構函式的調用。而且,在對變數進行初始化的場合下,變數本身的用途不言自明,所以在這裡定義變數有益於表明變數的含義。還記得在C語言中的做法嗎?每個變數的定義旁最好要有一條短注釋,以標明這個變數將來做什麼用。而現在,一個合適的名字(見條款28),再結合有意義的初始化參數,你就可以實現每個程式員的夢想:通過可靠的變數本身來消除對它不必要的注釋。
延遲變數定義可以提高程式的效率,增強程式的條理性,還可以減少對變數含義的注釋。看來是該和那些開放式模組的變數定義吻別了。