兩段構(gòu)造也是聲名狼藉得很,比之于MFC,好不了多少,貌似MFC中到處都是兩段構(gòu)造,難道兩段構(gòu)造的聲譽(yù)也是受MFC所累。定義完了一個(gè)對(duì)象變量之后,還要再調(diào)用一次該對(duì)象的Create函數(shù),而且還要Create成功了之后,才能對(duì)該對(duì)象做進(jìn)一步的操作,否則對(duì)象將一直處于非法狀態(tài)。這種代碼方式寫起來確實(shí)很惡心,為何不直接在構(gòu)造函數(shù)中直接Create,不成功就拋出異常,然后對(duì)象就流產(chǎn)了,好過它半死不活地一直茍延殘喘于世上,累己累人。其實(shí),MFC選擇兩段構(gòu)造也是有苦衷:1、先是很久很久以前,VC編譯器對(duì)異常的支持不怎么好,當(dāng)然,現(xiàn)在的VC編譯器,自然今時(shí)不比往日,但是,還要兼容以往的代碼;2、然后是MFC的設(shè)計(jì),它只是對(duì)API做了一層薄薄的包裝,薄薄的意思,就是,不管怎么搗鼓,都難以將WINDOWS系統(tǒng)中的各種對(duì)象包裝成一個(gè)干凈的C++對(duì)象了,因?yàn)椋珹PI本身就采用兩段構(gòu)造。可不是嗎?定義一個(gè)句柄變量,然后CreateXXX返回結(jié)果,返回值非法,表示創(chuàng)建失敗。失敗了,還要霸王硬上弓,后果會(huì)怎么樣,這誰也不知道。
理論上,構(gòu)造函數(shù)拋出異常確實(shí)很優(yōu)雅,代碼也更具美感,并且,其行為也更加明確,要么就處理,要么,就等著程序異常退出。但是,實(shí)際上,異常這種東西,真正實(shí)現(xiàn)執(zhí)行起來,卻相當(dāng)?shù)睦щy。更何況,如果完全丟棄兩段法,除了異常,還會(huì)引入一些新的問題,正所謂:“前門驅(qū)虎,后門進(jìn)狼”,進(jìn)來不只是一只狼,而是好幾只。生活的奧妙,就在于制造出新的問題,以解決舊的問題。
構(gòu)造函數(shù)中直接調(diào)用Create,就表示了用戶一定義一個(gè)類型變量,程序就會(huì)馬上啟動(dòng)Create函數(shù),也就意味著可能將創(chuàng)建窗口對(duì)象、內(nèi)核對(duì)象、甚至啟動(dòng)新的線程等等,這些操作都不是省油的燈,構(gòu)造函數(shù)中做了太多事情,會(huì)有隱藏太多細(xì)節(jié)之嫌,代碼本來就是為了隱藏細(xì)節(jié),這個(gè)多事之罪名暫且不論;但是,用戶沒法對(duì)創(chuàng)建過程Say NOT,也即是說,用戶一定義對(duì)象變量,就只能接受它的高昂的創(chuàng)建過程。難道,一開始就讓對(duì)象進(jìn)入有效狀態(tài),這都有錯(cuò)嗎?確實(shí)是的。有時(shí)候,用戶只是先想聲明(定義)對(duì)象,等必要(時(shí)機(jī)成熟)的時(shí)候,再讓它進(jìn)入有效狀態(tài)。咦,用戶這樣寫代碼,不太好吧,應(yīng)該強(qiáng)制他/她等到了那個(gè)時(shí)候,再定義對(duì)象變量。變量怎么可以隨隨便便就定義呢?應(yīng)該在要使用的時(shí)候,才定義它,這才是良好的代碼風(fēng)格。但是,有些情況,確實(shí)需要先暫時(shí)定義非法狀態(tài)下的對(duì)象變量,比如,這個(gè)對(duì)象是另一個(gè)對(duì)象(擁有者)的成員變量時(shí),那也沒什么,強(qiáng)制用戶在必要的時(shí)候,才定義擁有者對(duì)象變量。但是,假如這個(gè)擁有者必須是全局變量,那該怎么辦?那也沒什么,將擁有者定義為指針變量就是了?好了,本來只是要對(duì)象創(chuàng)建失敗的情況,現(xiàn)在還要考慮內(nèi)存分配的細(xì)節(jié),然后接著就是new delete,然后就是各種智能指針閃亮登臺(tái)演出,更糟糕的是,對(duì)象有效無效的問題依然沒有根除,因?yàn)椋灰胫羔槪看问褂弥羔槪捅仨殭z查指針是否有效,咦,難道操作空指針不會(huì)拋出異常嗎?C++規(guī)范中,操作空指針屬后果未確定的行為,對(duì)C++而言,未確定往往就是最糟糕的意思。此外,鑒于對(duì)象只能一直處于有效狀態(tài),它就不可能提供讓對(duì)象進(jìn)入無效狀態(tài)的操作。如果想要讓對(duì)象無效,唯一的辦法,就是讓它死去,強(qiáng)制對(duì)象啟動(dòng)析構(gòu)函數(shù),方法是離開作用域強(qiáng)者delete它。下次要使用它的時(shí)候,就再new一次或者定義一次,不,它已經(jīng)是另外一條新生命了。但是,對(duì)于兩段構(gòu)造的對(duì)象,只須Destroy又或者Create,對(duì)象可以永遠(yuǎn)只有一個(gè)。此外,二段構(gòu)造頗具擴(kuò)展性,很輕易地就可搞成三段構(gòu)造,每一步,用戶都有選擇的權(quán)利。但構(gòu)造異常就沒有這個(gè)優(yōu)點(diǎn)。
考慮到構(gòu)造函數(shù)中的參數(shù)問題,比如,月份的參數(shù),大家都知道,有效值只在1-12月之間。不討論這種情況下,非法的參數(shù)傳遞是否屬于代碼的邏輯問題。對(duì)此,構(gòu)造異常指導(dǎo)下的對(duì)象是不可能出現(xiàn)無參(沒有參數(shù)或者參數(shù)都有缺省值)的構(gòu)造函數(shù),因此,它們也都不能用于數(shù)組,難以應(yīng)用于全局變量、靜態(tài)變量、作為其他對(duì)象的數(shù)據(jù)成員,如果非要在這些場合下使用它們,比如占位符的作用,唯有用上指針,于是伴隨而來的,又如上文所述,使用指針之前,必須檢查指針的有效性,只怕不會(huì)比檢查二段構(gòu)造的有效性好多少。
二段構(gòu)造不輕易剝奪用戶的權(quán)利,提供更多選擇,可用于數(shù)組、堆棧、STL中的容器,要它死,它就死,要它活,它就活,但是,它可以從來都未曾消失過,要做的,僅僅是在使用它時(shí),清楚它是死是活就行了,不過多加幾次判斷而已。相比之下,構(gòu)造異常就更具侵入性了,一旦用上,就只能被迫遵照它的規(guī)則行事。
其實(shí),兩段構(gòu)造與構(gòu)造異常,都很惡心,只要一處代碼中用到了它,所有與之相關(guān)的代碼都沒法脫身。差別不過在于誰比誰惡心而已,這個(gè),視各人的口味而不同。對(duì)于本人這種害怕分配內(nèi)存,釋放內(nèi)存,更加畏懼異常的人來說(這并不表示本人寫不出異常安全的代碼),當(dāng)然優(yōu)先選擇二段構(gòu)造,MORE EFFECTIVE的條款中,聲稱,如無必要,不要提供缺省的構(gòu)造函數(shù),以免對(duì)象陷入半死不活的狀態(tài)中。而我的習(xí)慣作法則是,如無必要,必須提供缺省的構(gòu)造函數(shù),不要輕易剝奪用戶想要使用對(duì)象數(shù)組的權(quán)利,或者是由于不提供缺省的構(gòu)造函數(shù),而由此引起的種種不便。
好了,既然程序中決定用二段構(gòu)造了,那么,假如用戶定義了一個(gè)對(duì)象,忘了再構(gòu)造一次,但是又要執(zhí)行其他操作,怎么辦?嗯,那也沒什么,既然用戶不遵守契約,我們的對(duì)象自然可以做出種種不確定的行為。當(dāng)然,別忘了,在其他的每一個(gè)操作上都添加幾條assert語句,盡管這很惡心,也聊勝于無,減少點(diǎn)罪惡感,以便于在調(diào)試版中找出問題。