你經(jīng)常要使用構(gòu)造函數(shù)或者析構(gòu)函數(shù)來(lái)定義某個(gè)類型的一個(gè)變量,當(dāng)系統(tǒng)在運(yùn)行至變量的定義時(shí),就會(huì)引入一次構(gòu)造的開銷;在變量達(dá)到自身作用域的邊界時(shí),就會(huì)引入一次析構(gòu)的開銷。未使用的變量也會(huì)帶來(lái)一定的開銷,所以你應(yīng)該盡可能的避免這種浪費(fèi)的出現(xiàn)。
你可能會(huì)想你永遠(yuǎn)也不會(huì)定義變量而不去使用,但是你可能需要三思而后行。請(qǐng)觀察下邊的函數(shù),它在所提供的密碼足夠長(zhǎng)時(shí),可以返回一個(gè)加密版本的密碼。如果密碼長(zhǎng)度過(guò)短,函數(shù)就會(huì)拋出一個(gè)logic_error類型的異常(這個(gè)異常類型定義于標(biāo)準(zhǔn)C++庫(kù)中,參見條目54):
// 這個(gè)函數(shù)定義"encrypted"變量的時(shí)機(jī)過(guò)早
std::string encryptPassword(const std::string& password)
{
using namespace std;
string encrypted;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
... // 對(duì)密碼加密
return encrypted;
}
本函數(shù)中,盡管對(duì)象encrypted并不是完全未使用的,但是在拋出異常的情況下,函數(shù)就不會(huì)使用它。也就是說(shuō),即使encryptPassword拋出一個(gè)異常,你也要為encrypted付出一次構(gòu)造和一次析構(gòu)的代價(jià)。因此,你最好推遲encrypted的定義,直到你確認(rèn)你需要它時(shí)再進(jìn)行:
// 這個(gè)函數(shù)推遲了encrypted的定義,直到真正需要它時(shí)再進(jìn)行
std::string encryptPassword(const std::string& password)
{
using namespace std;
if (password.length() < MinimumPasswordLength) {
throw logic_error("Password is too short");
}
string encrypted;
... // 對(duì)密碼加密
return encrypted;
}
上面的代碼還沒(méi)有那么嚴(yán)謹(jǐn),這是因?yàn)樵诙xencrypted時(shí)沒(méi)有為它設(shè)置任何初始化參數(shù)。這就意味著編譯器將調(diào)用它的默認(rèn)構(gòu)造函數(shù)。通常情況下,你要對(duì)一個(gè)對(duì)象需要做的第一件事就是為它賦一個(gè)值,通常是通過(guò)一次賦值操作。條目4中解釋了為什么使用默認(rèn)構(gòu)造函數(shù)構(gòu)造對(duì)象并為其賦值,要比使用需要的值對(duì)其進(jìn)行初始化的效率低一些。那里的分析符合此處的情況。比如說(shuō),可以假設(shè)的較困難的部分是通過(guò)下面的函數(shù)來(lái)解決的:
void encrypt(std::string& s); // 適時(shí)為s加密
encryptPassword就應(yīng)該以下面的方式來(lái)實(shí)現(xiàn)了,盡管它不是最優(yōu)秀的:
// 這一函數(shù)推遲了enctypted定義的時(shí)機(jī),直到需要時(shí)才進(jìn)行。
// 但仍然會(huì)帶來(lái)不必要的效率問(wèn)題。
std::string encryptPassword(const std::string& password)
{
... // 同上,檢查密碼長(zhǎng)度
std::string encrypted; // encrypted的默認(rèn)構(gòu)造函數(shù)版本
encrypted = password; // 對(duì)encrypted賦值
encrypt(encrypted);
return encrypted;
}
更好的一種實(shí)現(xiàn)方式是,使用password來(lái)初始化encrypted,這樣就可以跳過(guò)默認(rèn)構(gòu)造過(guò)程所帶來(lái)的無(wú)謂的性能開銷:
// 最后給出定義和初始化encrypted的最佳方法
std::string encryptPassword(const std::string& password)
{
... // 檢查長(zhǎng)度
std::string encrypted(password); // 通過(guò)拷貝構(gòu)造函數(shù)定義和初始化
encrypt(encrypted);
return encrypted;
}
此時(shí)標(biāo)題中的“越晚越好”的真正含義就十分明顯了。你不僅僅要推遲一個(gè)變量的定義時(shí)機(jī),直到需要它時(shí)再進(jìn)行;你還需要繼續(xù)推遲,直至你掌握了它的初始化參數(shù)為止。這樣做,你就可以避免去構(gòu)造和析構(gòu)不必要的對(duì)象,你也可以避免那些無(wú)關(guān)緊要的默認(rèn)構(gòu)造過(guò)程。還有,通過(guò)初始化這些變量,定義這些變量的目的一目了然,從而代碼也變得更加清晰。
“但是循環(huán)呢?”你可能會(huì)想。如果一個(gè)變量?jī)H僅在循環(huán)題中使用,那么更好的選擇是:將它定義在循環(huán)題的外部,在每次循環(huán)迭代前對(duì)其進(jìn)行賦值;還是:在循環(huán)體的內(nèi)部定義變量?也就是說(shuō),哪種基本結(jié)構(gòu)是更優(yōu)秀的呢?
// 方法A:在循環(huán)體外部定義
Widget w;
for (int i = 0; i < n; ++i){
w = 取決于i的某個(gè)值;
...
}
// 方法B: 在循環(huán)體內(nèi)部定義
for (int i = 0; i < n; ++i) {
Widget w(取決于i的某個(gè)值);
...
}
這里我使用了Widget類型的對(duì)象,而不是string類型的對(duì)象,從而避免了進(jìn)行構(gòu)造、析構(gòu)、或者對(duì)象賦值等過(guò)程帶來(lái)的誤差。
對(duì)于Widget的操作而言,上面兩種方法所帶來(lái)的開銷如下:
l 方法A:1個(gè)構(gòu)造函數(shù) + 1個(gè)析構(gòu)函數(shù) + n次賦值。
l 方法B:n個(gè)構(gòu)造函數(shù) + n個(gè)析構(gòu)函數(shù)。
對(duì)于那些一次賦值操作比一對(duì)構(gòu)造-析構(gòu)操作開銷更低的類而言,方法A是較高效的。尤其是在n較大的情況下。否則方法B就是更好的選擇。還有,方法A使得w位于一個(gè)比方法B更大的作用域中,這是違背程序的可讀性和可維護(hù)性原則的。因此,除非你確認(rèn): (1)賦值操作比一對(duì)構(gòu)造-析構(gòu)操作更高效,(2)當(dāng)前代碼是對(duì)性能敏感的;其他任何情況下,你都應(yīng)該使用方法B。
時(shí)刻牢記
l 定義變量的時(shí)機(jī)越晚越好。這可以提高程序的清晰度和工作效率。