《EffectiveC++》讀書筆記(二)條款4-6

正文

Item 4: Make sure that objects are initialized before they’re used

想要完成這個Item很簡單,就是落在我們的構造函數上,因爲它的作用便是掌控類的初始化,這裏的關鍵是將每一個成員變量都初始化

分清什麼是初始化,什麼是賦值

首先需要指出的是,初始化(initialize)和賦值(assign)並不一樣。這讓我想起了本學期開始學的Java。
在Java課上和Java書上都是這樣寫構造方法的:

public class A{
    public A(int b){
        this.a = b;
    }
    private int a;
}

但是在寫C++時確很少這樣,我們往往都是這樣寫

//Plan A
class A{
public:
    A(int a, string b):a_(a),b_(b){}
private:
    int a_;
    string b_;
};

當然C++也可以這樣寫

//Plan B
class A{
public:
    A(int a, string b){
        this->a = a; 
        this->c = b;
    }
    int a;
    string c;
};

那麼問題來啦,在C++中這兩種構造函數的寫法有區別嗎??

答案是有的,在Plan A中,我們通過成員初始化列(member initialization list)來進行初始化,而Plan B則是進行了賦值(assign)。

哪種更好?爲什麼?

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

在Plan B 中, 成員變量在被賦值之前已經完成了初始化,如果這個變量是一個對象(如這裏的string),那麼它先調用它的default構造函數進行初始化,然後進入類A的構造函數體,再進行賦值,可以看到,前一步調用的default構造函數完全被浪費了。而在Plan A中,則直接調用對象的copy構造函數進行初始化。

大多數情況下,調用default構造函數+copy賦值運算符比只調用copy構造函數效率要低,所以我們更提倡Plan A的寫法。

當然,如果是一個內置類型(如int)就不存在效率的問題,但爲了一致性,還是把它也這樣寫吧。
同樣,如果你需要一個成員對象在構造函數中只default構造,那麼不加參數就可以了。
並且對於一個const成員或者reference,它們一定要初值,而不能被賦值。
無論如何,爲了避免出錯,在初始化列中列出所有成員變量

由於C++成員初始化順序固定,基類早於派生類被初始化,類中的成員變量按照其聲明順序進行初始化。所以我們在初始化列中的成員變量順序並不影響其初始化順序,我們應該在成員初始化列中也按照其聲明順序進行初始化

關於初始化順序的補充

首先給出結論:不同編譯單元內的non-local static 對象的初始化順序不確定

編譯單元指的是可以編譯爲.o文件的代碼。
static對象就是全局對象,在namespace作用域的對象;類內,函數內,文件作用域內的static對象。
non-local對象就是不在函數內定義的對象。

而由於這樣的對象初始化順序不確定,當我們兩個這樣的對象初始化具有依賴關係時,可以通過用一個返回static對象的引用的函數來解決

Object &getMyObject() // 是不是想到了單例模式。。。
{
    static Object myObject;
    return myObject;
}

這樣就保證需要myObject時它一定被初始化了,不用它就不會初始化。

Item 5: Know what functions C++ silently writes and calls.

當我們編寫自己的類時,編譯器會爲我們聲明一些public inline函數,包括default 構造函數,copy構造函數,copy assignment操作符,和析構函數。
注意這僅僅是聲明,只有當它們被調用時,編譯器纔會把它們實現。

編譯器定義的構造函數和析構函數會依次執行成員變量的構造函數(這裏的構造函數是無參的,)和析構函數。copy構造函數,copy assignment操作符則會將non-static成員一個個的copy過去。

但編譯器定義的函數必須保證其生成的代碼合法且有意義

如果一個成員對象的構造函數是個private,或者沒有無參的構造函數,那麼自然不合法。
如果一個成員對象是一個派生類,而其基類的copy構造函數爲private,那麼由於派生類會先處理基類成員(調用基類的copy構造函數),自然也不合法。
如果一個成員變量爲對象的引用,那麼修改這個引用則改變其對象,萬一有其他的引用也引用了這個對象呢?如果一個成員變量是const呢,該如何賦值,這樣的代碼便沒有意義。。。

所以遇到這樣的情況編譯器會拒絕爲其生成函數,而如果你的代碼中又需要這樣的操作。。。那就會報錯的。。。

Item 6 :Explicitly disallow the use of compiler-generated functions you do not want.

對於某些類來說,其不應該被copy或賦值。

比如,讓我們來編寫魔獸爭霸3的代碼,那麼劍聖(英雄)和大G(獸族步兵)便是兩種不同的對象,對於兩個步兵來說,它們其實除了血量來說沒有什麼不同,而劍聖只有一個,連名字都是獨一無二的。

又或者說,做手機的有很多,但Jobs只有一個。

陳碩的文章裏提到了值語義和對象語義,我認爲就是這樣的,對於值來說它是可以被copy或賦值的,而對於一個對象來說,它是獨一無二的,那麼它不應該被copy或賦值。

既然不應該,那麼我就需要明確的指出,讓編譯器幫助我們進行限制(類似的思想有很多,如 const,overwide或者Java中的@overwide,都是編譯器在幫助我們)。

書中主要是用了將不需要的函數聲明爲private ,(什麼。你說不需要不聲明就好了,請看上一條item),或者編寫一個nocopyable的類,在類裏將不需要的函數聲明爲private來繼承(原因依然參照上一條item)。

畢竟本書寫於2005年,而陳碩的muduo中也是採用繼承boost::noncopyable,在C++11中我們直接將其聲明爲=delete就行了。

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