大家都是成年人了,所以用不著我來告訴你們:使用未被初始化的物件無異於蠻幹。事實上,關於這個問題的整個想法會讓你覺得可笑;建構函式可以確保物件在建立時被初始化,難道不是這樣嗎?
唔,是,也不是。在某個特定的被編譯單元(即,原始檔)中,可能一切都不成問題;但如果在某個被編譯單元中,乙個物件的初始化要依賴於另乙個被編譯單元中的另乙個物件的值,並且這第二個物件本身也需要初始化,事情就會變得更複雜。
例如,假設你已經寫了這樣乙個程式庫,它提供乙個檔案系統的抽象,其中可能包括乙個功能,使得網際網路上的檔案看起來就象在本地一樣。既然程式庫使得整個世界看起來象乙個單獨的檔案系統,你就可以在程式庫的名字空間(見條款28)中建立乙個專門的物件,thefilesystem,這樣,使用者任何時候需要和程式庫所提供的檔案系統互動,都可以使用它:
class filesystem ; // 在個類在你
// 的程式庫中
filesystem thefilesystem; // 程式庫使用者
// 和這個物件互動
因為thefilesystem表示的是很複雜的東西,所以它的構造重要而且必需;在thefilesystem還沒構造之前就使用它會造成不可確定的行為。(然而,參考條款m17,象thefilesystem這樣的物件,其初始化可以被有效、安全地延遲。)
現在假設某個程式庫的使用者建立了乙個類,表示檔案系統中的目錄。很自然地,這個類使用了thefilesystem:
class directory ;
directory::directory()
進一步假設使用者想為臨時檔案專門建立乙個全域性directory物件:
directory tempdir; // 臨時檔案目錄
現在,初始化順序的問題變得很明顯了:除非thefilesystem在tempdir之前被初始化,否則,tempdir的建構函式將會去使用還沒被初始化的thefilesystem。但thefilesystem和tempdir是由不同的人在不同的時間、不同的檔案中建立的。怎麼可以確認thefilesystem在tempdir之前被建立呢?
任何時候,如果在不同的被編譯單元中定義了 "非區域性靜態物件" ,並且這些物件的正確行為依賴於它們被初始化的某一特定順序,這類問題就會產生。非區域性靜態物件指的是這樣的物件:
· 定義在全域性或名字空間範圍內(例如:thefilesystem和tempdir),
· 在乙個類中被宣告為static,或,
· 在乙個檔案範圍被定義為static。
很抱歉,"非區域性靜態物件" 這個術語沒有簡稱,所以你要讓自己習慣這種有點咬口的句子。
對於不同被編譯單元中的非區域性靜態物件,你一定不希望自己的程式行為依賴於它們的初始化順序,因為你無法控制這種順序。讓我再重複一遍:你絕對無法控制不同被編譯單元中非區域性靜態物件的初始化順序。
很自然地想知道,為什麼無法控制?
這是因為,確定非區域性靜態物件初始化的 " 正確" 順序很困難,非常困難,極其困難。即使在它最普通的形式下 ---- 多個被編譯單元,多個通過隱式模板例項化所生成的非區域性靜態物件(隱式模板例項化時,它們本身可能都會產生這樣的問題) ---- 不僅不可能確定正確的初始化順序,往往連找乙個可以確定正確順序的特殊情況都不值得。
在 "混沌理論" 領域,有乙個原理稱為 "蝴蝶效應" 。這條原理聲稱,世界某個角落的乙隻蝴蝶拍動翅膀,會對大氣產生微小的影響,從而導致某個遙遠的地方天氣模式的深刻變化。稍微準確一點來說也就是:對於某種系統,輸入的微小干擾會導致輸出徹底的變化。
軟體系統的開發也表現了自身的 "蝴蝶效應"。一些系統對需求的細節高度敏感,需求發生細小的變化,實現系統的難易程度就會發生巨大的變化。例如,條款29說明,將乙個隱式轉換的要求從 "string到char*" 改為 "string到const char*",就可以將乙個執行慢、容易出錯的函式用乙個執行快並且安全的函式來代替。
確保非區域性靜態物件在使用前被初始化的問題也和上面一樣,它對你的實現細節十分敏感。但是,如果你不強求一定要訪問 "非區域性靜態物件",而願意訪問具有和非區域性靜態物件 "相似行為" 的物件(不存在初始化問題),難題就消失了。取而代之的是乙個很容易解決的問題,甚至稱不上是乙個問題。
這種技術 ---- 有時稱為 "單一模式"(譯註:即singleton pattern,參見 "design patterns" 一書)---- 本身很簡單。首先,把每個非區域性靜態物件轉移到函式中,宣告它為static。其次,讓函式返回這個物件的引用。這樣,使用者將通過函式呼叫來指明物件。換句話說,用函式內部的static物件取代了非區域性靜態物件。(參見條款m26)
這個方法基於這樣的事實:雖然關於 "非區域性" 靜態物件什麼時候被初始化,c++幾乎沒有做過說明;但對於函式中的靜態物件(即,"區域性" 靜態物件)什麼時候被初始化,c++卻明確指出:它們在函式呼叫過程中初次碰到物件的定義時被初始化。所以,如果你不對非區域性靜態物件直接訪問,而用返回區域性靜態物件引用的函式呼叫來代替,就能保證從函式得到的引用指向的是被初始化了的物件。這樣做的另乙個好處是,如果這個模擬非區域性靜態物件的函式從沒有被呼叫,也就永遠不會帶來物件構造和銷毀的開銷;而對於非區域性靜態物件來說就沒有這樣的好事。
下面的**對thefilesystem和tempdir都採用了這一技術:
class filesystem ; // 同前
filesystem& thefilesystem() // 這個函式代替了
class directory ; // 同前
directory::directory()
directory& tempdir() // 這個函式代替了
系統被修改後,使用者還是完全和以前一樣程式設計,只是現在他們用的是thefilesystem()和tempdir(),而不是thefilesystem和tempdir。即,他們所用的是返回物件引用的函式,而不是物件本身。
這種返回引用的函式雖然採用了上面所討論的技術,但函式本身總是很簡單:第一行定義並初始化乙個區域性靜態物件,第二行返回它,僅此而已。因為太簡單,你可能很想把它宣告為inline。條款33指出,對於c++語言規範的最新修訂版本來說,這是乙個非常有效的實現策略;但它同時指出,在使用之前,一定要確認你的編譯器和標準中的相關要求要一致。如果編譯器不符合最新標準,你又象上面那樣使用內聯,就可能造成函式以及函式內部靜態物件有多份拷貝。這足以讓乙個成年的程式設計師哭泣。
確保非區域性靜態物件在使用前被初始化
唔,是,也不是。在某個特定的被編譯單元 即,原始檔 中,可能一切都不成問題 但如果在某個被編譯單元中,乙個物件的初始化要依賴於另乙個被編譯單元中的另乙個物件的值,並且這第二個物件本身也需要初始化,事情就會變得更複雜。例如,假設你已經寫了這樣乙個程式庫,它提供乙個檔案系統的抽象,其中可能包括乙個功能,...
條款04 確定物件在使用前已經被初始化
讀取未初始化的值會造成不明確的行為。例如下面這個建構函式 abentity abentity const std string name,const std string address,const std list phones thename name theaddress address th...
(1)確定物件被使用前已經被初始化
在物件使用之前將它初始化,對於無任何成員的內建型別,你必須手工完成此事。例如 int x 0 const char text double d std cin d 以input stream 的方式完成初始化 內建型別以外的任何其他東西,初始化責任落在建構函式身上。確保每乙個建構函式都將物件的每乙個...