使用未被初始化的物件無異於蠻幹。建構函式可以確保物件在建立時被初始化,難道不是這樣嗎?是,也不是。在某個特定的被編譯單元(即原始檔)中,可能一切都不成問題;但如果在某個被編譯單元中,乙個物件的初始化要依賴於另乙個被編譯單元中的另乙個物件的值,並且這第二個物件本身也需要初始化,事情就會變得更複雜。
例如,假設這樣乙個程式庫,它提供乙個檔案系統的抽象,其中可能包括乙個功能,使得網際網路上的檔案看起來就象在本地一樣。既然程式庫使得整個世界看起來象乙個單獨的檔案系統,就可以在程式庫的名字空間中建立乙個專門的物件,thefilesystem,這樣,使用者任何時候需要和程式庫所提供的檔案系統互動,都可以使用它:
class filesystem ; // 這個類在程式庫中
filesystem thefilesystem; // 程式庫使用者和這個物件互動
因為thefilesystem表示的是很複雜的東西,所以它的構造重要而且必需;在thefilesystem還沒構造之前就使用它會造成不可確定的行為。(然而,象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++語言規範的最新修訂版本來說,這是乙個非常有效的實現策略;但它同時指出,在使用之前,一定要確認你的編譯器和標準中的相關要求要一致。如果編譯器不符合最新標準,你又象上面那樣使用內聯,就可能造成函式以及函式內部靜態物件有多份拷貝。這足以讓乙個成年的程式設計師哭泣。
C 之全域性物件,區域性物件,靜態區域性物件
先說兩個概念 作用域 scope 和生命週期 lifetime 作用域 名字的作用域指的是知道該名字的程式文字區域 生命週期 物件的生命週期指在程式執行過程中物件存在的時間 全域性物件,顧名思義是全域性的物件,其作用域是整個程式文字,其物件的宣告週期是整個程式的執行過程 區域性物件 一般說的區域性變...
內部類(非靜態 區域性 靜態 匿名)
目錄 內部類 非靜態 區域性 靜態 匿名 一 非靜態內部類 1.建立非靜態內部類 2.在外部類中訪問內部類 3.在外部類外訪問內部類 4.在內部類中訪問外部類 5.補充 二 區域性內部類 1.區域性內部類的建立 2.在區域性內部類中訪問外部類成員變數 3.在區域性內部類中訪問外部類的區域性變數 4....
C 之區域性物件(自動物件和靜態區域性物件)
1 自動物件 預設情況下,區域性變數的生命期侷限於所在函式的每次執行期間。只有當定義它的函式被呼叫時才存在的物件稱為自動物件。自動物件在每次呼叫函式時建立和撤銷。該型別區域性變數儲存在棧上,在動態儲存區。區域性變數所對應的自動物件在函式控制經過變數定義語句時建立。如果在定義時提供了初始化,那麼每次建...