“zero 幫幫忙吧 ~~ ”
聽(tīng)到這個(gè)充滿(mǎn)誠(chéng)意的聲音,zero 垮下了雙肩,感覺(jué)自己處于徹底崩潰的邊緣 ———— 幾個(gè)月來(lái),每次 pisces 遇到什么不能解決的問(wèn)題,總是用這個(gè)開(kāi)場(chǎng)白來(lái)求 zero,而后 zero 就不得不面對(duì)各式各樣匪夷所思的古怪問(wèn)題。
“她怎么就能弄出那么多錯(cuò)誤來(lái)呢?” zero 在心中哀嘆。他苦著臉,問(wèn)道:“你就不能放過(guò)我去找 Solmyr 么?”
“可是 Solmyr 指定你來(lái)幫助我們的呀!”
“ …… 好吧,說(shuō)說(shuō)是什么問(wèn)題”,zero 一邊在心里再次痛罵了 Solmyr 一百遍,一邊做好了再次面對(duì)奇怪錯(cuò)誤的準(zhǔn)備。
“嗯,這里有一段代碼,是讀取配置文件信息的。現(xiàn)在只是個(gè)框架,將來(lái)可能需要讀好些配置文件,而且可能放在不同目錄下,所以這里我用一些 string 對(duì)象保存路徑和文件名,然后用 fopen 打開(kāi)。” 說(shuō)著,pisces 調(diào)出了一個(gè) cpp 文件:
#include <cstdio>
#include <string>
#include "config.h" // 頭文件,定義了存放配置信息的結(jié)構(gòu)
// 并包含 read_cfg 函數(shù)的聲明
using namespace std;
const string path = "./cfg/";
const string name = "system.cfg";
// 參數(shù)為指向保存配置信息結(jié)構(gòu)的指針
// 返回值為成功標(biāo)志,true 代表成功,false 代表失敗
bool read_cfg(CFG_DATA* p_data)
{
const string path_name = path+name;
FILE* fp = fopen(path_name.c_str(), "r");
if( fp == NULL )
return false;
// 使用 fp 讀取配置文件,放入 CFG_DATA 結(jié)構(gòu)
... ...
... ...
return true;
}
... ....
“在我這里運(yùn)行的好好的,但提交給測(cè)試組做單元測(cè)試的時(shí)候卻總是出毛病,說(shuō)是找不到配置文件,我去他們那里看過(guò)了,配置文件的名字和路徑都是對(duì)的呀,真是太奇怪了。”
zero
斜著眼看了看這段代碼,對(duì)其中的風(fēng)格大為不滿(mǎn):“我說(shuō) pisces,錯(cuò)誤先不提,你這段代碼的風(fēng)格實(shí)在不太象 C++
啊。首先參數(shù)應(yīng)該用引用,它的安全性可比指針高多了,而且你既然用了指針,就應(yīng)該用斷言做檢查么;其次你干嗎不用流來(lái)讀取文件呢?非要用 fopen
……”
“我知道我知道!” pisces 忙不迭的打斷了
zero,“我知道這里我的風(fēng)格不太好,可是無(wú)論怎樣這段打開(kāi)文件的代碼應(yīng)該沒(méi)錯(cuò)呀?可現(xiàn)在問(wèn)題是打開(kāi)文件的時(shí)候出錯(cuò),明明文件在那兒的,可函數(shù)總是返回
false 。幫我先把這個(gè) bug 找出來(lái)嗎,幫幫忙了 ~~~~~ ”
“好好!”,zero 搖了搖頭,開(kāi)始尋找錯(cuò)誤。5
分鐘過(guò)去,zero 的眉頭漸漸皺了起來(lái),這段只有三五行的代碼看起來(lái)一目了然,根本沒(méi)有隱藏錯(cuò)誤的地方。zero
把這段代碼引進(jìn)他用來(lái)測(cè)試的工程里,編譯連接,測(cè)試運(yùn)行,程序一切正常,正確的找到了配置文件。這是怎么回事?zero 迷惑了。
“zero?”
pisces 將探詢(xún)的眼光投向 zero。“呃 …… 別急,我們?nèi)y(cè)試組那里看看究竟怎么回事。” zero
心存僥幸,沒(méi)準(zhǔn)是測(cè)試組弄錯(cuò)了呢?10 分鐘之后,zero 垂頭喪氣的回來(lái)了,后面跟著
pisces。測(cè)試組沒(méi)有弄錯(cuò),在他們那里這段代碼確實(shí)不能正常工作,調(diào)試器顯示,作為文件名傳入 fopen
的是個(gè)空字符串。這是怎么回事?zero 一邊想著,一邊往自己的座位走去 ———— 哎?那個(gè)站在自己計(jì)算機(jī)前的人不是 …… 不是 ……
Solmyr 么?他手上拿的是 ……
zero 本能的感到了危險(xiǎn),猛的一偏頭!一個(gè)文件夾從離他的臉只有 0.01 公分的地方唰的一下擦了過(guò)去!可惜后面的 pisces 就沒(méi)有這么幸運(yùn)了,被打個(gè)正著!
Solmyr 看了一眼正捂著臉的 pisces,對(duì)嚇出一身冷汗的 zero 問(wèn)道:“屏幕上那段代碼不是你寫(xiě)的吧?”
“對(duì),是她寫(xiě)的。” zero 同情的看了看 pisces ,后者好象還沒(méi)有從打擊中恢復(fù)過(guò)來(lái) ……
“有因就有果,真是一點(diǎn)也不會(huì)錯(cuò)啊 ……” Solmyr 聳聳肩,“知道這段代碼為什么會(huì)出錯(cuò)么?”
zero 苦笑:“不知道。”
“把眼光放開(kāi)一點(diǎn),你看一下調(diào)用這個(gè)函數(shù)的地方就知道了。”
zero 調(diào)出了整個(gè)工程。按照文檔上的說(shuō)明,read_cfg 是整個(gè)系統(tǒng)初始化過(guò)程的步驟之一,當(dāng)系統(tǒng)啟動(dòng)時(shí)會(huì)讀取配置文件確定一些初始化的參數(shù)。據(jù)此,zero 很容易的找到了調(diào)用處,在另外一個(gè) cpp 文件中:
#include "config.h" // 其中聲明了 read_cfg 這個(gè)函數(shù)
class system
{
public:
// 完成系統(tǒng)啟動(dòng)時(shí)的初始化工作
system()
{
CFG_DATA data;
read_cfg(&data);
// 使用 data 中的信息配置系統(tǒng)
... ...
}
// 完成系統(tǒng)退出時(shí)的清理工作
~system()
{
... ...
}
... ...
};
system theSystem; // 代表整個(gè)系統(tǒng)的全局對(duì)象
Solmyr
清了清嗓子:“這是個(gè)看起來(lái)很干凈的手法。system
這個(gè)類(lèi)只有這里一個(gè)全局對(duì)象,這個(gè)全局對(duì)象代表了整個(gè)系統(tǒng),它構(gòu)造,系統(tǒng)做初始化工作;它析構(gòu),系統(tǒng)開(kāi)始做退出時(shí)的清理工作。全局對(duì)象的身份保證了它會(huì)在
進(jìn)入 main 函數(shù)之前構(gòu)造,在 main 函數(shù)退出之后析構(gòu)。這一招是你教 pisces 的吧?”Solmyr 看了看 zero 。
zero 點(diǎn)點(diǎn)頭:“沒(méi)錯(cuò),上次她問(wèn)我如何能夠比較好的處理初始化和清理的代碼,我想起了上次關(guān)于‘成對(duì)出現(xiàn) ’的討論(注一),就給她出了這個(gè)主意。”
“思路是對(duì)頭的,但是實(shí)現(xiàn)的方式不妥,毛病就出在全局對(duì)象上面。我問(wèn)你,一個(gè) cpp 文件,或者說(shuō)得正式一點(diǎn),一個(gè)編譯單元中的全局對(duì)象構(gòu)造析構(gòu)的順序是怎樣的?”
“ …… 應(yīng)該是按照定義它們的順序。” zero 努力的回憶一陣,很肯定的說(shuō)道。
“正確,那么不同編譯單元之間全局對(duì)象的構(gòu)造順序呢?”
“ …… 好象沒(méi)有明確的規(guī)則,這個(gè)應(yīng)該屬于標(biāo)準(zhǔn)未定義對(duì)吧?”
“正確,所以 ……”
“所
以 …… …… 啊!我明白了!哎呀!我怎么這么遲鈍!看到 fopen 傳入的是空字符串的時(shí)候我就應(yīng)該想到的!” zero
露出了恍然大悟的表情,“在打開(kāi)配置文件的代碼段中,保存文件名的 path 和 name 也是全局對(duì)象,換句話(huà)說(shuō),這兩個(gè) string 對(duì)象和
theSystem 對(duì)象的構(gòu)造次序是無(wú)法確定的。在測(cè)試組那里,theSystem 先于 path 和 name 構(gòu)造,所以當(dāng)
theSystem 的構(gòu)造函數(shù)調(diào)用 read_cfg 函數(shù)的時(shí)候,path 和 name 這兩個(gè) string
對(duì)象根本還沒(méi)有來(lái)得及構(gòu)造!當(dāng)然無(wú)法取出文件名和路徑來(lái)!而在我和 pisces 的計(jì)算機(jī)上,構(gòu)造次序與之相反,這段代碼就可以正確運(yùn)行。”
“很好,那么如何解決呢?”
“嗯
…… …… 我想只要盡可能避開(kāi)全局對(duì)象就行了,一方面 theSystem 這個(gè)對(duì)象可以放到 main
函數(shù)里,一樣可以保證正確完成初始化工作和清理工作;另一方面,read_cfg 那邊最好也不要用全局的 string
對(duì)象了,一樣可以用局部對(duì)象。這樣是能夠解決這個(gè)問(wèn)題 ……” zero
皺起了眉頭,顯然對(duì)這個(gè)解法還不太滿(mǎn)意,“那如果我有一些全局性質(zhì)的對(duì)象,而且希望精確的定義它們的構(gòu)造次序,該怎么辦呢?”
Solmyr 點(diǎn)了點(diǎn)頭:“沒(méi)錯(cuò),有時(shí)候這確實(shí)是個(gè)合理的要求。對(duì)此最簡(jiǎn)單的解法是‘被函數(shù)包裝的 static 對(duì)象’(注二),象這樣:”
system& theSystem()
{
static system instance;
return instance;
}
“instance 是個(gè) static 對(duì)象,這保證了它的生存期,然后它會(huì)在第一次調(diào)用這個(gè)函數(shù)的時(shí)候構(gòu)造。針對(duì)你的問(wèn)題,只要你聲明多個(gè)這樣的函數(shù),然后保證它們第一次調(diào)用的次序,就可以保證這些對(duì)象構(gòu)造的次序了。”
zero 若有所思的點(diǎn)了點(diǎn)頭。
“說(shuō)
起來(lái),次序問(wèn)題絕對(duì)不只這一個(gè),C++
中類(lèi)似的問(wèn)題相當(dāng)多。和這個(gè)問(wèn)題最接近的,是類(lèi)的成員和基類(lèi)的構(gòu)造次序,其他的還有表達(dá)式求值的次序、函數(shù)參數(shù)求值的次序,(注三)等等。遇到次序問(wèn)題,
千萬(wàn)不要想當(dāng)然,問(wèn)自己一句:這個(gè)次序有定義嗎?有定義的要遵守,無(wú)定義的要避開(kāi)。好了,這個(gè)問(wèn)題大概就是這樣,接下來(lái)你的任務(wù)是 …… ”
“我知道,把這些討論整理成文檔對(duì)吧?”
“不,是想辦法讓 pisces 搞懂這個(gè)問(wèn)題。”
“…… …… …… …… …… ……”
望著 Solmyr 甩手匆匆離去的背影,再看看身邊正用最拿手的“誠(chéng)懇”眼神看著自己的 pisces ,zero 突然泛起了一種奇怪的感覺(jué):好象以前只是單純被 Solmyr 砸的日子也沒(méi)有那么糟糕 …… …… ……