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一致。