鍍金池/ 教程/ C/ A.3 默認(rèn)函數(shù)
3.4 本章總結(jié)
6.3 基于鎖設(shè)計(jì)更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
6.1 為并發(fā)設(shè)計(jì)的意義何在?
5.2 <code>C++</code>中的原子操作和原子類型
A.7 自動(dòng)推導(dǎo)變量類型
2.1 線程管理的基礎(chǔ)
8.5 在實(shí)踐中設(shè)計(jì)并發(fā)代碼
2.4 運(yùn)行時(shí)決定線程數(shù)量
2.2 向線程函數(shù)傳遞參數(shù)
第4章 同步并發(fā)操作
2.3 轉(zhuǎn)移線程所有權(quán)
8.3 為多線程性能設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)
6.4 本章總結(jié)
7.3 對(duì)于設(shè)計(jì)無(wú)鎖數(shù)據(jù)結(jié)構(gòu)的指導(dǎo)建議
關(guān)于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)
4.5 本章總結(jié)
A.9 本章總結(jié)
前言
第10章 多線程程序的測(cè)試和調(diào)試
5.4 本章總結(jié)
第9章 高級(jí)線程管理
5.1 內(nèi)存模型基礎(chǔ)
2.5 識(shí)別線程
第1章 你好,C++的并發(fā)世界!
1.2 為什么使用并發(fā)?
A.5 Lambda函數(shù)
第2章 線程管理
4.3 限定等待時(shí)間
D.3 &lt;atomic&gt;頭文件
10.2 定位并發(fā)錯(cuò)誤的技術(shù)
附錄B 并發(fā)庫(kù)的簡(jiǎn)單比較
5.3 同步操作和強(qiáng)制排序
A.8 線程本地變量
第8章 并發(fā)代碼設(shè)計(jì)
3.3 保護(hù)共享數(shù)據(jù)的替代設(shè)施
附錄D C++線程庫(kù)參考
第7章 無(wú)鎖并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
D.7 &lt;thread&gt;頭文件
D.1 &lt;chrono&gt;頭文件
4.1 等待一個(gè)事件或其他條件
A.3 默認(rèn)函數(shù)
附錄A 對(duì)<code>C++</code>11語(yǔ)言特性的簡(jiǎn)要介紹
第6章 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
封面圖片介紹
7.2 無(wú)鎖數(shù)據(jù)結(jié)構(gòu)的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術(shù)
4.2 使用期望等待一次性事件
8.4 設(shè)計(jì)并發(fā)代碼的注意事項(xiàng)
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來(lái)的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關(guān)的錯(cuò)誤類型
D.4 &lt;future&gt;頭文件
3.2 使用互斥量保護(hù)共享數(shù)據(jù)
9.1 線程池
1.1 何謂并發(fā)
9.2 中斷線程
4.4 使用同步操作簡(jiǎn)化代碼
A.2 刪除函數(shù)
1.3 C++中的并發(fā)和多線程
1.4 開始入門
第5章 C++內(nèi)存模型和原子類型操作
消息傳遞框架與完整的ATM示例
8.2 影響并發(fā)代碼性能的因素
7.1 定義和意義
D.6 &lt;ratio&gt;頭文件
A.4 常量表達(dá)式函數(shù)
7.4 本章總結(jié)
1.5 本章總結(jié)
第3章 線程間共享數(shù)據(jù)

A.3 默認(rèn)函數(shù)

刪除函數(shù)的函數(shù)可以不進(jìn)行實(shí)現(xiàn),默認(rèn)函數(shù)就則不同:編譯器會(huì)創(chuàng)建函數(shù)實(shí)現(xiàn),通常都是“默認(rèn)”實(shí)現(xiàn)。當(dāng)然,這些函數(shù)可以直接使用(它們都會(huì)自動(dòng)生成):默認(rèn)構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),移動(dòng)構(gòu)造函數(shù),拷貝賦值操作符和移動(dòng)賦值操作符。

為什么要這樣做呢?這里列出一些原因:

  • 改變函數(shù)的可訪問性——編譯器生成的默認(rèn)函數(shù)通常都是聲明為public(如果想讓其為protected或private成員,必須自己實(shí)現(xiàn))。將其聲明為默認(rèn),可以讓編譯器來(lái)幫助你實(shí)現(xiàn)函數(shù)和改變?cè)L問級(jí)別。

  • 作為文檔——編譯器生成版本已經(jīng)足夠使用,那么顯式聲明就利于其他人閱讀這段代碼,會(huì)讓代碼結(jié)構(gòu)看起來(lái)很清晰。

  • 沒有單獨(dú)實(shí)現(xiàn)的時(shí)候,編譯器自動(dòng)生成函數(shù)——通常默認(rèn)構(gòu)造函數(shù)來(lái)做這件事,如果用戶沒有定義構(gòu)造函數(shù),編譯器將會(huì)生成一個(gè)。當(dāng)需要自定一個(gè)拷貝構(gòu)造函數(shù)時(shí)(假設(shè)),如果將其聲明為默認(rèn),也可以獲得編譯器為你實(shí)現(xiàn)的拷貝構(gòu)造函數(shù)。

  • 編譯器生成虛析構(gòu)函數(shù)。

  • 聲明一個(gè)特殊版本的拷貝構(gòu)造函數(shù),比如:參數(shù)類型是非const引用,而不是const引用。

  • 利用編譯生成函數(shù)的特殊性質(zhì)(如果提供了對(duì)應(yīng)的函數(shù),將不會(huì)自動(dòng)生成對(duì)應(yīng)函數(shù)——會(huì)在后面具體講解)。

就像刪除函數(shù)是在函數(shù)后面添加= delete一樣,默認(rèn)函數(shù)需要在函數(shù)后面添加= default,例如:

class Y
{
private:
  Y() = default;  // 改變?cè)L問級(jí)別
public:
  Y(Y&) = default;  // 以非const引用作為參數(shù)
  T& operator=(const Y&) = default; // 作為文檔的形式,聲明為默認(rèn)函數(shù)
protected:
  virtual ~Y() = default;  // 改變?cè)L問級(jí)別,以及添加虛函數(shù)標(biāo)簽
};

編譯器生成函數(shù)都有獨(dú)特的特性,這是用戶定義版本所不具備的。最大的區(qū)別就是編譯器生成的函數(shù)都很簡(jiǎn)單。

列出了幾點(diǎn)重要的特性:

  • 對(duì)象具有簡(jiǎn)單的拷貝構(gòu)造函數(shù),拷貝賦值操作符和析構(gòu)函數(shù),都能通過memcpy或memmove進(jìn)行拷貝。

  • 字面類型用于constexpr函數(shù)(可見A.4節(jié)),必須有簡(jiǎn)單的構(gòu)造,拷貝構(gòu)造和析構(gòu)函數(shù)。

  • 類的默認(rèn)構(gòu)造,拷貝,拷貝賦值操作符合析構(gòu)函數(shù),也可以用在一個(gè)已有構(gòu)造和析構(gòu)函數(shù)(用戶定義)的聯(lián)合體內(nèi)。

  • 類的簡(jiǎn)單拷貝賦值操作符可以使用std::atomic<>類型模板(見5.2.6節(jié)),為某種類型的值提供原子操作。

僅添加= default不會(huì)讓函數(shù)變得簡(jiǎn)單——如果類還支持其他相關(guān)標(biāo)準(zhǔn)的函數(shù),那這個(gè)函數(shù)就是簡(jiǎn)單的——不過,用戶顯式的實(shí)現(xiàn)就不會(huì)讓這些函數(shù)變簡(jiǎn)單。

第二個(gè)區(qū)別,編譯器生成函數(shù)和用戶提供的函數(shù)等價(jià),也就是類中無(wú)用戶提供的構(gòu)造函數(shù)可以看作為一個(gè)aggregate,并且可以通過聚合初始化函數(shù)進(jìn)行初始化:

struct aggregate
{
  aggregate() = default;
  aggregate(aggregate const&) = default;
  int a;
  double b;
};
aggregate x={42,3.141};

例子中,x.a被42初始化,x.b被3.141初始化。

第三個(gè)區(qū)別,編譯器生成的函數(shù)只適用于構(gòu)造函數(shù);換句話說,只適用于符合某些標(biāo)準(zhǔn)的默認(rèn)構(gòu)造函數(shù)。

struct X
{
  int a;
};

如果創(chuàng)建了一個(gè)X的實(shí)例(未初始化),其中int(a)將會(huì)被默認(rèn)初始化。

如果對(duì)象有靜態(tài)存儲(chǔ)過程,那么a將會(huì)被初始化為0;另外,當(dāng)a沒賦值的時(shí)候,其不定值可能會(huì)觸發(fā)未定義行為:

X x1;  // x1.a的值不明確

另外,當(dāng)使用顯示調(diào)用構(gòu)造函數(shù)的方式對(duì)X進(jìn)行初始化,a就會(huì)被初始化為0:

X x2 = X();  // x2.a == 0

這種奇怪的屬性會(huì)擴(kuò)展到基礎(chǔ)類和成員函數(shù)中。當(dāng)類的默認(rèn)構(gòu)造函數(shù)是由編譯器提供,并且一些數(shù)據(jù)成員和基類都是有編譯器提供默認(rèn)構(gòu)造函數(shù)時(shí),還有基類的數(shù)據(jù)成員和該類中的數(shù)據(jù)成員都是內(nèi)置類型的時(shí)候,其值要不就是不確定的,要不就是被初始化為0(與默認(rèn)構(gòu)造函數(shù)是否能被顯式調(diào)用有關(guān))。

雖然這條規(guī)則令人困惑,并且容易造成錯(cuò)誤,不過也很有用;當(dāng)你編寫構(gòu)造函數(shù)的時(shí)候,就不會(huì)用到這個(gè)特性;數(shù)據(jù)成員,通常都可以被初始化(指定了一個(gè)值或調(diào)用了顯式構(gòu)造函數(shù)),或不會(huì)被初始化(因?yàn)椴恍枰?:

X::X():a(){}  // a == 0
X::X():a(42){}  // a == 42
X::X(){}  // 1

第三個(gè)例子中①,省略了對(duì)a的初始化,X中a就是一個(gè)未被初始化的非靜態(tài)實(shí)例,初始化的X實(shí)例都會(huì)有靜態(tài)存儲(chǔ)過程。

通常的情況下,如果寫了其他構(gòu)造函數(shù),編譯器就不會(huì)生成默認(rèn)構(gòu)造函數(shù)。所以,想要自己寫一個(gè)的時(shí)候,就意味著你放棄了這種奇怪的初始化特性。不過,將構(gòu)造函數(shù)顯示聲明成默認(rèn),就能強(qiáng)制編譯器為你生成一個(gè)默認(rèn)構(gòu)造函數(shù),并且剛才說的那種特性會(huì)保留:

X::X() = default;  // 應(yīng)用默認(rèn)初始化規(guī)則

這種特性用于原子變量(見5.2節(jié)),默認(rèn)構(gòu)造函數(shù)顯式為默認(rèn)。初始值通常都沒有定義,除非具有(a)一個(gè)靜態(tài)存儲(chǔ)的過程(靜態(tài)初始化為0),(b)顯式調(diào)用默認(rèn)構(gòu)造函數(shù),將成員初始化為0,(c)指定一個(gè)特殊的值。注意,這種情況下的原子變量,為允許靜態(tài)初始化過程,構(gòu)造函數(shù)會(huì)通過一個(gè)聲明為constexpr(見A.4節(jié))的值為原子變量進(jìn)行初始化。