1. 程式人生 > >effective c++乾貨之條款04:確定物件被使用前已先被初始化

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物件必然會耗費這個成本。