effect C++ 確定對象被使用前先被初始化

將對象初始化

int x 在某些語境下x保證初始化(爲0),但在其他語境中卻不保證。

class Point{
  int x , int y;
}
 ....
 Point p ;

p的成員變量有時候初始化(爲0),有時不會。讀取未初始化的值會導致不明確行爲。

最佳的處理辦法就是:永遠在使用對象之前將他們初始化。對於無任何成員的內置類型,必須手工完成此事。

int x =0;        
const char * text ="A-C-style string";
double d ;
std::cin>>d;      //以讀取input stream的方式完成初始化

確保每一個構造函數都將對象的每一個成員初始化。

注意別混淆了賦值和初始化。


class PhoneNumber {...}
class ABEntry{
public:
  ABEntry(const std::string& name,const std::string&address,const std::list<PhoneNumber>&phones);
private:
   std:: string theName;
   std:: string theAddress;
   std:: list<PhoneNumber>thePhones;  
   int numTimesConsulted;
};
ABEntry::ABEntry(const std::string&name,const std::string &address,const std::list<PhoneNumber>&phones)
{
    theName=name;         //這些都是賦值
    theAddress=address;   //而非初始化
    thePhones=phones;
    numTimesConsulted=0;
}


C++規定,對象的成員變量的初始化動作發生在進入構造函數本體之前

較佳的一個寫法是:

ABEntry::ABEntry(const std:: string &name,const std::string&address,  
const std::list<PhoneNumber>&phone):theName(name),theAddress(address),thePhones(phones),numTimesConsulted(0) //這些都是初始化
{}

通常效率更高,基於賦值的那個版本首先調用default構造函數爲成員賦初值,然後立刻重新賦值。

總使用成員初值列。

不同編譯單元內定義之non-local static 對象

static對象 ,其壽命從構造出來直到程序結束爲止(stack和heap-based對象都被排除)。這種對象包括global對象,定義於namesapce作用域的對象、在classes內、函數內、以及在file作用域內被聲明爲static的對象。 函數內的static對象被稱爲local static對象(因爲它們對函數而言是local),其他的對象稱爲non-local對象。程序結束時static對象會被自動銷燬,它們的析構函數會在main()結束時調用。

編譯單元是產出單一目標文件的那些源碼,基本是單一源碼加上其所含入的頭文件。

現在至少有兩個源碼文件,每一個內含至少一個non-local static 對象(也就是該對象是global或位於namespace作用域內,抑或在class內或file作用域內被聲明爲static)。真正的問題是:如果某編譯單元內某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,它所用到的這個對象可能未被初始化。

例:

class FileSystem{
public:
...
std::size_t numDisks()const; //衆多成員函數之一
...
}
extern FileSystem tfs; //預備給客戶使用的對象
假設用戶建立一個class

class Directory{
public:
 Directory(params);
 ...
};
Directory::Directory(params)
{
 ...
 std::size_t disks=tfs.numDisks(); //使用tfs對象
 ...
}
如果客戶決定創建一個Director對象

Director tempDir (params);
除非tfs在tempDir之前被初始化,否則tempDir的構造函數會用到尚未初始化的tfs。它們是定義在不同編譯單元內的non-local static 對象。

解決:
將每個non-local static 對象搬到自己的專屬函數內(該對象在此函數內被聲明爲static)。這些函數返回一個reference 指向它所含的對象。然後用戶調用着這些函數,而不是指涉這些對象。 non-local static 對象被 local static 對象替換了。(Singleton模式的一個常見的實現手法)

C++保證,函數內的local static 對象會在“該函數被調用期間”“首次遇上該對象之定義式”時被初始化。所以如果以“函數調用”(返回一個reference指向local static對象)替換“直接訪問non-local static對象”,就可以保證獲得的那個reference將指向一個歷經初始化的對象。如果從未調用 non-local static 對象的“仿真函數”,就絕不會引發構造和析構成本。

class FileSystem{...}    //同前
FileSystem& tfs()        //這個函數用來替換tfs對象;它在FileSystem class 中可能是個static
{
  static FileSystem fs;  //定義並初始化一個local static對象
  return fs;             //返回一個reference指向上述對象
}
class Directory{...};      //同前
Directory::Directory(params)
{
   ...
   std::size_t disks =tfs().numDisks();//原本的reference to tfs現在改爲tfs()
}
Directory&tempDir()    //這個函數用來替換tempDir對象,它在Directory中可能是一個static  
{
  static Directory td;   //定義並初始化local static 對象
  return td;            //返回一個reference指向上述對象
}

這樣,客戶使用tfs()和tempDir()而不再是tfs和tempDir,使用的是指向static對象的reference


爲內置型對象進行手工初始化,因爲C++不保證初始化它們。

構造函數最後使用成員初值列,而不要在構造函數本體內使用賦值操作。初值列列初的成員列表,其排列次序應該和它們在class中聲明的次序相同。

爲免除“跨編譯單元之初始化次序”問題,請以local static 對象替換non-local static 對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章