條款04 確定物件被使用前已先被初始化

2021-07-01 18:22:56 字數 4778 閱讀 3215

總結:

1. 為內建型物件進行手工初始化,因為c++不保證初始化它們。

2. 建構函式最好使用成員初值列(memberinitialization list),而不要在建構函式本體內使用賦值操作(assignment)。初值列列出的成員變數,其排列次序應該和它們在class中的宣告次序相同。

3. 為免除"跨編譯單元之初始化次序"問題,請以local static物件替換non-local static物件。

關於"將物件初始化"這事,c++ 似乎反覆無常。在某些語境下內建型別和類的成員變數保證被初始化,但在其他語境中卻不保證。

讀取未初始化的值會導致不明確的行為。它可能讓你的程式終止執行,可能汙染了正在進行讀取動作的那個物件,可能導致不可測知的程式行為,以及許多令人不愉快的除錯過程

最佳處理辦法就是:永遠在使用物件之前先將它初始化。無論是對於內建型別、指標還是讀取輸入流,你必須手工完成此事:

int x = 0;   //int的手動初始化

const char* text = "a c-style string"; //對指標進行手工初始化

double d;

std::in >> d; //以讀取方式初始化

內建型別以外的任何其他東西,初始化則由建構函式完成,確保每乙個建構函式都將物件的每乙個成員初始化

這個規則很容易奉行,重要的是別混淆了賦值和初始化。考慮乙個用來表現通訊簿的class,其建構函式如下:

class phonenumber ; 

class abentry ;

abentry::abentry(const std::string& name, const std::string& address,

const std::list& phones)

這會導致abentry物件帶有你期望(你指定)的值,但

不是最佳做法。c++ 規定,物件的成員變數的初始化動作發生在進入建構函式本體之前。在abentry建構函式內,thename, theaddress和thephones都

不是被初始化,而是被賦值。初始化的發生時間

更早,發生於這些成員的

default建構函式被自動呼叫之時(比進入abentry建構函式本體的時間更早)。

使用所謂的member initialization list(成員初始化列表)替換賦值動作會更好:

abentry::abentry(const std::string& name, const std::string& address,

const std::list& phones)

:thename(name),

theaddress(address), //現在,這些都是初始化(initializations)

thephones(phones),

numtimesconsulted(0)

//現在,建構函式本體不必有任何動作

這個建構函式和上乙個的最終結果相同,但

使用建構函式的初始化列表通常效率較高。基於賦值的那個版本首先呼叫defalt建構函式為成員變數賦值,然後立刻再對它們賦予新值;default建構函式所做的一切都因此浪費了;本例中成員初始化列表避免了這個問題,因為初值列中針對各個成員變數而設的實參,被拿去作為各個成員變數之建構函式的實參。

對大多數型別而言,比起先呼叫default建構函式然後再呼叫copy assignment操作符,單隻呼叫一次copy建構函式是比較高效的,有時甚至高效得多。對於內建型物件如numtimesconsulted,其初始化和賦值的成本相同,但為了一致性最好也通過成員初值列來初始化。同樣道理,甚至當你想要default構造乙個成員變數,你都可以使用成員初值列。假設abentry有乙個無引數建構函式,我們可將它實現如下:

abentry::abentry():thename(),  //呼叫thename的default建構函式;  

theaddress(), //為theaddress做類似動作;

thephones(), //為thephones做類似動作;

numtimesconsulted(0) //記得將numtimesconsulted顯式初始化為0

當成員是內建型別,一定要使用成員初始化列表,如果成員變數是const或reference,一定需要初值,不能被賦值;為避免需要記住成員變數何時必須在成員初始化列表中被初始化,何時不需要,最簡單的做法是:總是在成員初始化列表中列出所有成員,並總使用成員初值列

c++ 成員初始化次序base classes早於其derived classes,而class的成員變數總是以其宣告次序被初始化(不是在成員列表中出現的次序)。回頭看看abentry,其thename成員永遠最先被初始化,然後是theaddress,再來是thephones,最後是numtimesconsulted,即使它們在成員初值列中以不同的次序出現。為避免某些可能存在的晦澀錯誤(兩個成員變數的初始化帶有次序性,如初始化array時需要指定大小,因此代表大小的那個成員變數必須先有初值),當你在成員初值列中條列各個成員時,最好總是以其宣告次序為次序。

static物件

函式內的static物件稱為local static物件,其他static物件稱為non-local static物件。程式結束時static物件會被自動銷毀,也就是它們的析構函式會在main()結束時被自動呼叫。

編譯單元(translation unit)

產出單一目標檔案(single object file)的那些原始碼,基本上它是單一原始碼檔案加上其所含入的標頭檔案(#include files)。

真正的問題是

如果某編譯單元內的某個non-localstatic物件的初始化動作使用了另一編譯單元內的某個non-local static物件,它所用到的這個物件可能尚未被初始化,因為c++ 對"定義於不同編譯單元內的non-local static物件"的初始化次序並無明確定義。

假設你有乙個filesystem class,它讓網際網路上的檔案看起來好像位於本機(local)。由於這個class使世界看起來像個單一檔案系統,你可能會產出乙個特殊物件,位於global或namespace作用域內,象徵單一檔案系統:

class filesystem ;  

extern filesystem tfs; //預備給客戶使用的物件,tfs代表"the file system"

現在假設某些客戶建立了乙個class用以處理檔案系統內的目錄(directories)。很自然他們的class會用上thefilesystem物件:

class directory ;  

directory::directory( params )

進一步假設,這些客戶決定建立乙個directory物件,用來放置臨時檔案:

directory tempdir( params );    //為臨時檔案而做出的目錄

除非tfs在tempdir之前先被初始化,否則tempdir的建構函式會用到尚未初始化的tfs。但tfs和tempdir是不同的人在不同的時間於不同的原始碼檔案建立起來的,它們是定義於不同編譯單元內的non-local static物件。c++ 對"定義於不同的編譯單元內的non-localstatic物件"的初始化相對次序並無明確定義。這是有原因的:決定它們的初始化次序相當困難,非常困難,根本無解。

乙個小小設計便可完全消除這個問題將每個non-localstatic物件搬到自己的專屬函式內,並將該物件在此函式內被宣告為static,這些函式返回乙個reference指向它所含的物件。然後使用者呼叫這些函式,而不直接指涉這些物件。換句話說,non-local static物件被local static物件替換了。這是singleton模式的乙個常見實現手法。

c++ 保證,函式內的local static物件會在該函式被呼叫期間首次遇上該物件之定義式時被初始化。如果你從未呼叫non-local static物件的"**函式",就絕不會引發構造和析構成本!

以此技術施行於tfs和tempdir身上,結果如下:

class filesystem ;   //同前  

filesystem& tfs() //這個函式用來替換tfs物件;它在

class directory ; //同前

directory::directory( params )//同前,但原本的reference to tfs

directory& tempdir() //這個函式用來替換tempdir物件;

這麼修改之後,這個系統程式的客戶唯一不同的是他們現在使用tfs()和tempdir()而不再是tfs和tempdir,也就是說他們

使用函式返回的"指向static物件"的references,而

不再使用static物件自身。這些函式內含static物件的事實使它們在多執行緒系統中帶有不確定性。

條款04 確定物件被使用前已先被初始化

讀取未初始化的值會導致不明確的行為,使程式終止或者行為不可 最佳的處理方法是 永遠在使用物件之前先將它初始化。在初始化時,有以下注意事項 對內建型別,需要手工完成初始化。對其他型別,確保建構函式都將物件的每乙個成員初始化。規定總是在初值列表中列出所有的成員變數,以免需要區分哪些成員變數無需初值。成員...

條款04 確定物件被使用前已先被初始化

條款04 確定物件被使用前已被初始化 include include include using std string using std cout using std endl using std list class point04 1.不要混淆了賦值 assignment 和初始化 initi...

條款04 確定物件被使用前已先被初始化

最佳的處理方法 永遠在使用物件的之前先將他初始化,對於無任何成員的內建型別,你必須手工完成此時 對於內建型別以外的任何其他東西,初始化責任落在建構函式身上 確保每乙個建構函式都將對物件的每乙個成員初始化。但是別混淆賦值 assignment 和初始化 initialization 內建型別如下 in...