2018-12-23

---

toc:

? ? depth_from: 1

? ? depth_to: 6

? ? ordered: false

html:

? ? embed_local_images: true

? ? embed_svg: true

? ? offline: false

? ? toc: true

print_background: false

export_on_save:

? ? html: true

---

# 讓自己習慣C++

## 確定對象使用前已經初始化

1. 內置類型

? ? 對于內置類型,賦值和初始化成本相同。建議使用前賦初始值。

2. 對于自定義類型

? ? C++規定,類中成員變量的初始化動作發生在進入構造函數本體之前。如果直接在構造函數中直接賦值,成員變量是經歷過初始化和然后在構造函數中賦值兩個操作。

? ? 所以建議,在構造函數中使用成員初始化列表(member initialization list)。

? ? ```C++

? ? class MyObject

? ? {

? ? ? ? public:

? ? ? ? ? ? Object(Object object, int x)

? ? ? ? ? ? ? ? :object(object),

? ? ? ? ? ? ? ? x(x)

? ? ? ? ? ? {}

? ? ? ? ? ? Object()

? ? ? ? ? ? ? ? :object(),

? ? ? ? ? ? ? ? x(0)

? ? ? ? ? ? {}

? ? ? ? private:

? ? ? ? ? ? Object object;

? ? ? ? ? ? int x;

? ? }

? ? ```

? ? 注意:如果有構造函數沒有入參,記得把成員內置對象初始化。同時,成員初始化列表中變量順序并不是初始化順序,而是成員變量在類中定義的順序決定。為了避免迷惑,建議和定義順序保持一致。

3. non-local static對象

? ? C++對定義不同編譯單元內的non-local static對象的初始化次序沒有明確定義。而相反,對local static對象初始化時機有明確規定,就是對一次調用它的時候。

? ? 注:編譯單元是產生單一目標文件的一些源碼,基本是它的單一源碼文件和include的頭文件。

? ? 所以,使用包含local static對象的reference-returning函數來代替non-local static對象。

? ? ```C++

? ? Object& Object()

? ? {

? ? ? ? static Object object;

? ? ? ? return object;

? ? }

? ? ```

? ? 但是,在多線程中因為這些reference-returning函數含有static對象,導致行為不確定。應該來說,只要是non-const static對象在多線程都有問題。???

? ? 建議:在程序單線程啟動階段,手工調用所有的reference-returning函數,這樣就可消除與初始化有關的競速問題了。

# 構造/析構/賦值運算

## 請給基類聲明virtual析構函數

### 問題

例如,factory函數一般返回基類指針,指針指向heap中的一片內存。但是最后使用完畢后delete對調用基類的析構函數。

但是C++指出,**當derived對象經由base指針delete掉,而該基類帶有一個no-virtual析構函數,其行為未定義。一般是調用基類的析構函數,銷毀掉基類部分。這樣就導致內存泄露的問題。**

### 方法

請給為了**多態用途**的基類聲明virtual析構函數。

一般的,一個class帶有virtual函數,表示它被當作base class。所以,任何class只要帶有virtual函數幾乎應該有一個virtual析構函數。

**注意:有時想把一個class聲明為抽象類,但手頭上沒有pure virtual函數,可以把析構函數設為pure virtual。因為抽象類一般是多態用途的base類,而base類幾乎都建議析構是virtual的**

并非所有base類都是多態用途,例如uncopyable類,這些基類就用需要virtual析構函數。

### 原理

如果析構函數不是virtual,則調用的函數在編譯時已經確定,由于指針是基類指針,所以調用的就是基類的析構函數;

如果析構函數是virtual,則調用的函數在運行期間確定,對象會有一個虛表指針,指向一個虛表數組,元素是函數指針,指向對應的子類virtual函數,所以調用的是子類的析構函數。

## Uncopyable類

### 問題

### 方法

* 方法一

? ? 把拷貝構造函數和賦值操作符聲明為私有的。

? ? 優點是實現簡單,但是類的成員函數和友元函數可以使用私有函數,解決方法是拷貝構造函數和賦值操作符只聲明不定義,這樣在鏈接期會報錯。

? ? ```C++

? ? class MyClass{

? ? ? ? private:

? ? ? ? ? ? MyClass(const MyClass&);? ? ? ? ? ? //只聲明,不定義

? ? ? ? ? ? MyClass& operator=(const MyClass&); //只聲明,不定義

? ? ? ? ...

? ? }

? ? ```

* 方法二

? ? 繼承禁止拷貝類Uncopyable。這樣如果使用MyClass類的拷貝構造函數或者賦值操作符,會調用基類對應的函數,由于基類是私有函數,則編譯器會報錯。

? ? 優點是把鏈接期報錯問題提前到編譯期。

? ? ```C++

? ? class Uncopyable{

? ? ? ? protected:

? ? ? ? ? ? Uncopyable(){}

? ? ? ? ? ? ~Uncopyable(){}

? ? ? ? private:

? ? ? ? ? ? Uncopyable(const Uncopyable&);

? ? ? ? ? ? Uncopyable& operator=(const Uncopyable&);

? ? }

? ? class MyClass:public Uncopyable{}

? ? ```

? ? **或者你可以使用Boost提供的版本,叫做noncopyable的class。**

# 資源管理

## RAII類管理資源(13 14)

為防止資源泄露,請使用RAII(Resource Acquisition Is Initialization,取得資源就初始化,被銷毀就釋放資源)對象。

### 智能指針

兩個常被使用的RAII類是tr1::shared_ptr(regerence-counting smart pointer引用計數型智能指針)和auto_ptr(智能指針)。

* auto_ptr,是個類指針對象,也就是所謂的智能指針,含義是當指針銷毀,會自動delete所指對象。注意,拷貝時會把對象地址傳給拷貝者,而原有指針為null。

? ? ```C++

? ? void f()

? ? {

? ? ? ? std::auto_ptr<Resource> pRsc1(resourceFactory());

? ? ? ? /*使用pRsc資源*/

? ? ? ? std::auto_ptr<Resource> pRsc2(pRsc1);//現在pRsc2指向資源,而pRsc1為null

? ? ? ? pRsc1 = pRsc2;//現在pRsc1指向資源,而pRsc2為null

? ? ? ? /*運行到最后銷毀,會調用Resource析構函數釋放資源*/

? ? }

? ? ```

* tr1::shared_ptr,是regerence-counting smart pointer引用計數型智能指針。與auto_ptr不同的是,可以拷貝,只有當所有指針都銷毀時,delete所指對象。**通常share_ptr是RAII類的最佳選擇**

? ? ```C++

? ? void f()

? ? {

? ? ? ? std::tr1::shared_ptr<Resource> pRsc1(resourceFactory());

? ? ? ? /*使用pRsc資源*/

? ? ? ? std::auto_ptr<Resource> pRsc2(pRsc1);//現在pRsc1、pRsc2都指向資源

? ? ? ? pRsc1 = pRsc2;//同上,無任何改變

? ? ? ? /*運行到最后,當pRsc1、pRsc2都銷毀,會調用Resource析構函數釋放資源*/

? ? }

? ? ```

? ? 當然,如果釋放資源不是簡單的delete內存,還需要做其他事情,比如文件關閉,數據庫關閉,這時智能指針shared_ptr可以指定某一函數為刪除器。形式是:`std::tr1::shared_ptr<Resource> pRsc1(resourceFactory(), deleter)`其中deleter是某一函數指針,當沒有智能指針指向該資源,會調用deleter函數。**而auto_ptr則沒有這個設定**。

注意:這兩個智能指針都是delete對象,當對象是個對象數組時,則不會調用delete[],則可以使用boost::scoped_array和boost::shared_array類來代替。

### 自定義RAII類

自定義的好處是可以定義想要的行為。

首先看下RAII類:

```C++

class ResourceManage

{

? ? public:

? ? ? ? explicit ResourceManage()

? ? ? ? {

? ? ? ? ? ? pRsc = resourceFactory();

? ? ? ? }

? ? ? ? ~ResourceManage()

? ? ? ? {

? ? ? ? ? ? resouceDestroy(pRsc);

? ? ? ? }

? ? private:

? ? ? ? Resource *pRsc;

}

```

自定義行為:

1. 禁止復制

? ? 方法是,使用繼承禁止拷貝類來實現`class ResourceManage:private Uncopyable{...}`

2. 對底層資源使用引用計數

? ? 方法是,RAII內部的私有資源指針換成share_ptr來實現

3. 轉移底部資源的擁有權

? ? 方法是,把RAII內部的私有資源指針換成auto_ptr來實現

4. 復制底部資源

? ? 方法是,重載拷貝構造函數和賦值運算符

## 通過RAII類使用資源

RAII類并不是封裝資源,而是為了確保資源釋放一定會發生。但是由于把資源或資源指針作為私有成員,這個類也阻礙我們對資源的使用。例如:

```C++

class Resource

{

? ? ...

? ? doSomeThing(){...}//資源類有個api會對資源做些事情

? ? ...

}

void f()

{

? ? ResourceManage rscMag;//資源獲取

? ? rscMag->doSomeThing()//錯誤!!!doSomeThing不是ResourceManage的成員方法

}

```

方法:

1. 顯示轉換

? ? 顯示的提供get函數,把內部資源或資源指針返回。

? ? 注意,由于ResourceManage類不是為了封裝資源,所以通過公有函數把私有成員返回也很正常。這樣不僅隱藏客戶不需要看到的部分,但同時也為客戶全面準備好所有東西。

? ? ```C++

? ? rscMag.get()->doSomeThing()

? ? ```

? ? 相似地,智能指針tr1::shared_ptr和auto_ptr也有get成員函數,把自己轉為普通指針。

2. 隱式轉換

? ? RAII類,改良版

? ? ```C++

? ? class ResourceManage

? ? {

? ? ? ? public:

? ? ? ? ? ? explicit ResourceManage()

? ? ? ? ? ? {

? ? ? ? ? ? ? ? pRsc = resourceFactory();

? ? ? ? ? ? }

? ? ? ? ? ? ~ResourceManage()

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resouceDestroy(pRsc);

? ? ? ? ? ? }

? ? ? ? ? ? operator Resource() const{return pRsc;}//定義隱式轉換Resource類型函數

? ? ? ? private:

? ? ? ? ? ? Resource *pRsc;

? ? }

? ? ```

注意:

? ? 雖然隱式轉換更符合書寫,但是畢竟是隱私轉換,可能增加錯誤轉換的風險。所以一般顯式轉換比較安全,隱式轉換比較方便。


## 以獨立語句將newed對象置入智能指針

用RAII類管理資源,或多或少的使用智能指針。而資源對象初始化后賦值給智能指針,需要單獨一句。如下:

```C++

//這里資源初始化和使用放到一條語句中。

//C++并沒有定義同一條語句,邏輯上沒有關聯的步驟的執行先后順序。

//如果在new Resource執行后執行getPara()異常終端,資源指針還沒有傳給智能指針。這樣會導致后面資源始終沒有釋放。

doSomeThing(std::tr1::shared_ptr<Resource>(getResource()), getPara());

```

應該這樣做:

```C++

std::tr1::shared_ptr<Resource> pRsc(getResource());//像這些關鍵步驟最后單獨一個語句

doSomeThing(pRsc, getPara());

```

注意:其中getResource是factory函數,其返回Resource的指針。為了防止用戶沒有及時把指針傳給智能指針,可以把factory函數設計成返回智能指針。

# 設計與聲明

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

比如一個接口是設置日期,但是參數容易誤用。

```C++

void setDate(int month, int day, int year);//setDate的聲明

setDate(30, 3, 1999);//錯誤,應該是3, 30, 1999的

setDate(3, 32,1995);//錯誤,3月沒有32號

```

一種方法是把入參class化或者struct化。

```C++

void setDate(const Month& m, const Day& d, const Year& y);//setDate的聲明

struct Day{

? ? explicit Day(int d):val(d){}

? ? int val;

}

...

setDate(Day(30), Month(3), Year(1995));

```

但有時候,參數表示的意義不能用基本類型來表示。比如獲取日期的其中一個字段。

```C++

int getNumByField(String field);

int year = getNumByField("yeer");//錯誤,應該是year的

```

大部分人會想到使用宏或者枚舉,但是這些都是基本類型,還是容易犯錯。可以使用如下:

```C++

int getNumByField(const DateField& dateField);

class DateField{

? ? public:

? ? ? ? static DateField year(){return DateField("year");}

? ? ...

? ? private:

? ? ? ? explicit Month(String field):field(field){};

? ? ? ? String field;

}

getNumByField(DateField.year());

```

## 用pass-by-reference 替換 pass-by-value

### 問題

```c++

//函數聲明

void doSomeThing(Base b);

class Base{};

class Drive:public Base{};

//函數使用

void doSomeThing(Drive d);

```

其中Drive類為實參,而Base類為形參。參數傳遞時會調用Base類的拷貝構造函數,進而會把Drive的特性丟失掉,造成“切割問題”。

### 方法

用pass-by-reference 替換 pass-by-value。其實C++編譯器底層就是用指針實現引用的。改為指針也能解決,但會引入指針相關的問題。

注意:內置類型大部分比指針簡單,所以內置類型還是推薦使用pass-by-value

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • 1.C和C++的區別?C++的特性?面向對象編程的好處? 答:c++在c的基礎上增添類,C是一個結構化語言,它的重...
    杰倫哎呦哎呦閱讀 9,575評論 0 45
  • 1. C++基礎知識點 1.1 有符號類型和無符號類型 當我們賦給無符號類型一個超出它表示范圍的值時,結果是初始值...
    Mr希靈閱讀 18,030評論 3 82
  • 3 資源管理 所謂資源就是,一旦用了它,將來必須還給系統。C++程序中最常使用的資源就是動態分配內存(如果分配內存...
    暗夜望月閱讀 411評論 0 0
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,532評論 1 51
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9