筆記:Effective C++

03.const

1.const的修飾,mutable可以釋放變量的const屬性

記住就近原則

另外:

void f1(const Widget *pw);

void f2(Widget const *pw);

以上兩者是等價的。

2.迭代器是以指針爲根據塑造出來的,所以迭代器的作用就像個T*指針。聲明迭代器爲const就像聲明指針爲const一樣(T* const),表示迭代器本身不能被修改,即不能指向不同的東西,但它指向的值是可以改動的。如果你希望迭代器所指的東西不可被修改,那就需要使用const_iterator;

(1)const std::vector<int>::iterator iter = vec.begin(),這是T* const,指針不可以變,指向的內容可以變

(2)std::vector<int>::const_iterator cIter = vec.begin();這是const T*,指向的內容不可以改變

3.兩個成員函數如果只是常量性不同,可以被重載。

4.函數後面加const

編譯器會自動給每一個函數加一個this指針。在一個類的函數後面加上const後,就表明這個函數是不能改變類的成員變量的(加了mutable修飾的除外.實際上,也就是對這個this指針加上了const修飾。

04.確定對象被使用前先初始化

讀取未初始化的值會導致不明確的行爲,爲此,永遠在使用對象之前對其初始化。

內置類型(c part of c++)需要手動初始化,如:int x = 0;因爲c++不保證初始化它們,對於(non-C parts of c++)會被進行默認的初始化

內置類型以爲的,都是構造函數對其初始化。

C++規定,對象的成員變量的初始化動作發生在進入構造函數本體之前。因此如果構造函數在函數體內進行賦值,之此前,已經調用了default構造函數進行初始化,這是多餘的。

更加高效的做法是:成員初始化列表(member initialization list)來替代賦值操作。

c++有着十分固定的“成員初始化次序”: base classes 更早於其drived classes被初始化,而class的成員變量總是以其聲明的次序被初始化,與初始化列表中成員的次序無關。

staic對象與stack和heap-based對象不同,它應當是在數據區。被聲明爲static的對象。直到程序結束時才被銷燬。

爲避免“跨編譯單元之初始化次序”問題,最好以local static(定義在函數內static 對象)來替代non-local static對象,因爲不保證他們初始化的次序


05:瞭解c++默默編寫並調用哪些函數

一個class,如果你自己沒有聲明,編譯器會爲它聲明一個copy構造函數,一個copy assignment操作符和一個析構函數。所以這些函數都是public且inline,如果你沒有聲明任何構造函數,編譯器也會爲你聲明一個default構造函數,注意:編譯器產生的析構函數是個no-virtual函數,如果class的base class聲明瞭virtual析構函數,纔會被聲明爲virtual

class Empty {};
//等價如下
class Empty{
public:
Empty() {...}
Empty(const Empty & rhs){...}
~Empty(){...}
Empty& operator=(const Empty& rhs){....}
}

一旦你聲明瞭一個構造函數,編譯器就不會再爲它創建default構造函數。

引用(reference)爲對象起了另外一個名字,引用必須初始化,初始化時,引用和它的初始值綁定在一起,而不是將初始值拷貝給引用。一旦初始化完成,引用將和它的初始值綁定,引用無法再綁定到另外一個對象,因此必須初始化。

因此當編譯器面對“內含const成員”和“內含reference成員”時,將不會爲其夠找copy assignment,你必須自己定義copy assignment


06:若不想使用編譯器自動生成的函數,就該明確拒絕

如果你不想使用編譯器自動產生的copy構造函數和copy assignment操作,那麼最好的方法就是“將成員函數聲明爲private且故意不實現它們”,編譯器生成的是public函數,因此對象可以直接調用,聲明爲private,將無法調用,另外,由於你以及聲明瞭自己的版本,編譯器將不會在爲你構造


07:爲多態基類聲明virtual析構函數

當drived class 對象經由一個base class指針被刪除,而該base class帶着non-virtual 析構函數,其結果未定義——實際調用的是base class的析構函數,只銷毀base class部分,對象的derived成分沒有被銷燬。

解決的方法:任何class只要帶有virtual函數都幾乎確定應該有一個virtual析構函數。

但是:如果class不含有virtual函數,令其析構函數爲virtual往往會帶來額外的問題

class Point{
public:
    Point(int xCoord, int yCoord);
    ~Point();
private:
    int x, y;
};

沒有帶vitual,那麼該clas Point可以被認爲是一個64bits的對象。但如果Point的析構函數是virtual,那麼就不再是64bits

爲了實現virtual函數,對象必須攜帶某些信息,主要用來在運行期決定哪個virtual函數該被調用。由vptr(virtual table pointer)指針指出。vptr指向一個由函數指針構成的數據(vtbl:virtual table);每一個帶有virtual函數的class都有一個相應的vtbl。

因此,內含virtual函數的class,其對象的體積會增加。這樣Point對象不再能夠塞入一個64bit的緩衝器。

結論:只有當class內含有至少 virtual函數,才爲它聲明virtual析構函數

class AWOV {
public:
    virtual ~AWOV() = 0;
};

這個class有一個pure virtual函數,所以它是個抽象class,又由於它有個virtual析構函數,因此不用擔心上述的析構函數問題。但注意:必須爲這個pure virtual析構函數提供一份定義

AWOV::~AWOV(){}//pure virtual析構函數的定義
析構函數被調用的次序與構造函數被調用的次序剛好相反,析構函數:最深層次的派生(most drived)的那個class其析構函數最先被調用,然後是其每一個base class的析構函數被調用。


08:別讓異常逃離析構函數

09:絕不在構造和析構過程中調用virtual函數

base class構造期間virtual函數絕不會下降到derived classes階層。取而代之的是,對象的作爲就像錄屬base類型一樣。在base class構造期間,virtual函數不是virtual函數

理由:base class的構造函數的執行更早於derived class構造函數,如果此時下降到derived class,derived class中的成員變量還沒有別構造,顯然是錯誤的。


10:令operator=返回一個reference to *this

Widget& operator=(const Widget& rhs){
   ...
   return *this;
}
這是一個協議,不僅適合=,也適合+=, -=, *=等等


11:在operator=中處理“自我賦值”

指針,引用,常常會爲一個對象帶來一個別名,“有一個以上的方法指稱某對象”,有時base class的reference或指針可以指向一個derived class對象,這也有可能是別名

那麼賦值x=y就有可能是自我賦值。自我複製會帶來一些不安全的操作,比如先釋放x,然後爲其賦值,明顯錯誤。

<pre name="code" class="cpp">Widget& Widget::operator=(const Widget& rhs){
 Bitmap *pOrig = pb;
 pb = new Bitmap(*rhs.pb);
 delete pOrig;
 return *this;
}


先檢測是否爲自我賦值操作,但更好的方式如上述

12:複製對象時勿忘其每一個成分

如果你不使用編譯器提供的copying函數,自己提供copying函數,那麼當你的copying函數有明顯的錯誤時,編譯器也不會報錯。

因此,如果你爲class添加一個成員變量,你必須同時修改copying函數。

每當“爲derived class 撰寫copying函數”時,必須小心地複製其base class成分。那部分往往是private,應該讓derived class的copying函數調用相應的base class函數。因此編寫coying函數:

(1)複製所有local成員變量

(2)調用所有base classes內的適當的copying函數

13:以對象管理資源

把資源放進對象內,我們便可依賴C++的“析構函數自動調用機制”確保資源被釋放.

auto_ptr是個“類指針對象”即“智能指針”,其析構函數自動對其所指對象調用delete。

void f() {
    std::auto_ptr<Inverstment> pInv(createIverstment());
    std::auto_ptr<Inverstment> pInv2(pInv1);//pInv2指向對象,pInv1被設爲null
    pInv1 = pInv2;//pInv1指向對象,pInv2被設爲null
}

auto_ptr被銷燬時會自動刪除它所指之物,所以一定要注意別讓多個auto_ptr同時指向同一個對象。

注意:若通過copy構造函數或copy assignment操作符複製它們,它們會變成NULL,而複製所得的指針將取得資源的唯一擁有權。

tr1::shared_ptr是RCSP,所謂RCSP也是智能指針,持續追蹤共有多少對象指向某筆資源,並在無人指向它時自動刪除該資源。tr1::shared_ptr不會像auto_ptr那樣出想copy的異常反應。

14:在資源管理類中小心copying行爲

16:成對使用new和delete時要採取相同形式
當你使用new(動態生成一個對象時),有兩件事情發生。第一,內存被分配出來。第二,針對次內存會有一個(或更多)構造函數被調用。

當使用delete時,也有兩件事情會發生,第一,針對次內存會有一個(或者多個)析構函數被調用,然後內存才被釋放。

數組所用的內存通常還包括“數組大小”的記錄,以便需要知道調用多少次析構函數,單一的內存則沒有這筆記錄。

如果你new時調用了[ ],那麼你在delete時,也要使用[ ],告訴編譯器,它是一個數組,需要多次調用析構函數。如果new不使用[ ],delete也不應該使用


18:讓接口容易被正確使用,不易被誤用

除非有好的理由,否則應該儘量令你的types的行爲與內置types一致。

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