effective c++乾貨之條款04:確定物件被使用前已先被初始化
1. 總是用成員初始化列初始化成員變數。
int x;
//在某些情況下,x可能被初始化為0,也可能不被初始化,出現隨機值
class MyClass
{
private:
int x;
int y;
};
...
MyClass p;
//在某些情況下,x,y可能被初始化為0,也可能不被初始化,出現隨機值
雖然C++對於何時會自動初始化有一定的規則,但是規則過於繁瑣,所以最佳的解決辦法就是任何時候都要手動的初始化物件。
//對於沒有成員的內建型別,必須手動初始化
int x = 0;
char Buf[1024] = {0};
首先不能混淆初始化和賦值。
C++規定:物件的成員變數的初始化發生在進入建構函式本體之前。
注意:這裡說的成員變數必須本身是一個有建構函式的物件,對於內建型別的成員變數C++並不保證。
//賦值操作 class MyClass { private: int SomeValue; std::string SomeString; std::list<int> SomeList; public: MyClass(const std::string &SomeString, const std::list<int> &SomeList); }; MyClass::MyClass(const std::string &String, const std::list<int> &List) { //在進入此建構函式之前,成員變數SomeString, SomeList已經被自身的預設建構函式初始化 //以下僅為賦值操作 SomeString = String; SomeList = List; SomeValue = 0; }
也就是說,在進入建構函式本體之前,本身是物件的成員變數SomeString,SomeList會被自身的default(預設建構函式)函式初始化,在進入建構函式時,就相當於對物件賦值,而不是初始化,那麼之前所有物件的預設初始化都浪費了。
改進方法:使用成員初始列替換賦值操作。
//初始化操作 class MyClass { private: int SomeValue; std::string SomeString; std::list<int> SomeList; public: MyClass(const std::string &String, const std::list<int> &List); }; MyClass::MyClass(const std::string &String, const std::list<int> &List) :SomeString(String), SomeList(List), SomeValue(0) //這些都是初始化操作 { //函式體內為空 }
此時,MyClass建構函式的實參會成為SomeString, SomeList物件中建構函式的實參,進行copy構造,這樣就省去呼叫若干個default建構函式的時間,效率會有所提升。
對於內建型別成員,初始化和賦值成本相同(但如果內建型別為const或者reference,則必須使用初始化而不可賦值),但為了一致性,也在初始化列中初始化。
如果某個類具有多個建構函式,這些建構函式都有自己的成員初始化列,並且這個類中具有多個成員變數,那麼就可能帶來大量的重複。解決方法是將那些“初始化和賦值成本相同”的成員變數放在一個private的函式中賦值並供所有建構函式呼叫。
C++的成員初始化順序是按照成員的宣告順序進行的,而和成員初始化列的順序無關。
2. 不同編譯單元用local static物件替換non-local static物件。
編譯單元:指產生單一目標檔案的原始碼。
local static:指函式內的static物件。
non-local static:除了local static都是non-local static物件。
對於不同編譯單元的兩個non-local static物件,c++是不保證誰會先被初始化的,如果其中一個物件A需要另外一個物件B初始化,而此時B尚未初始化,就會有所麻煩。
//在檔案A中
class MyClassA
{
...
public:
int Fun() const; //成員函式
...
};
extern MyClassA A;
//在檔案B中
class MyClassB
{
...
public:
MyClassB();
...
};
MyClassB::MyClassB()
{
int SomeThing = A.Fun(); //呼叫物件A
}
MyClass B(); //建立一個B類物件
此時初始化次序就先的尤為重要了,如果物件A沒有被初始化,直接用來初始化B,程式就會出現意想不到的錯誤,所以我們需要確定A在B初始化之前已經被初始化。但C++並沒有對不同編譯單元的兩個non-local static物件的初始化次序提供任何保證。
解決方法:將non-local static改成local static
//在檔案A中
class MyClassA
{
...
public:
int Fun() const; //成員函式
...
};
MyClassA &A() //用一個函式建立一個local-static物件並且返回其引用
{
static MyClassA a;
return a;
}
//在檔案B中
class MyClassB
{
...
public:
MyClassB();
...
};
MyClassB::MyClassB()
{
int SomeThing = A().Fun(); //呼叫物件A
}
MyClassB &B()
{
static MyClassB b;
return b;
}
C++保證:函式內的local static物件會在其函式呼叫期間,首次遇到該物件定義式的時候被初始化;所以在MyClassB中,如果以函式的形式呼叫物件A,則肯定先會經歷一次物件A的初始化,從而避免災難。
另一個優點是:如果從未呼叫建立物件的函式,那麼還會省去物件構造和析構的成本,而non-local static物件必然會耗費這個成本。