本文主要介紹《深度探索 C++對象模型》之《構造函數語義學》中的 Copy Constructorios
首先須要明確,構造函數什麼時候會被調用呢?cppreference 中已經有了足夠詳細地說明:c++
凡在對象從同類型的另外一對象(以直接初始化或複製初始化)初始化時,調用複製構造函數(除非重載決議選擇了更好的匹配或其調用被消除),狀況包括:
初始化:T a = b; 或 T a(b);,其中 b 類型爲 T;
函數實參傳遞:f(a);,其中 a 類型爲 T 而 f 爲 Ret f(T t);
函數返回:在如 T f() 這樣的函數內部的 return a;,其中 a 類型爲 T,它沒有移動構造函數。數組
在以前《構造函數語義學——Default Constructor 篇》一文中,咱們分析了編譯器產生 default constructor 的條件,以及編譯器所產生的 default constructor 的類型(trivial & non-trivial);對於構造函數來講,其原理也是大體相似的,只是具體的細節條件不一樣,此文中就再也不給出具體的證實,讀過前一篇博文的讀者也應該可以本身分析,此文只給出具體的條件函數
隱式聲明的複製構造函數
若不對類類型(struct、class 或 union)提供任何用戶定義的複製構造函數,則編譯器始終會聲明一個複製構造函數,做爲其類的非 explicit 的 inline public 成員。spa
與 default constructor 相似,只要沒有任何 user_declared 的 copy constructor,那麼編譯器就會爲咱們自動聲明一個 copy constructor(這一點與《深度探索 C++對象模型》中所述不一樣)指針
隱式定義的複製構造函數
若隱式聲明的複製構造函數未被棄置,則當其被 ODR 式使用時,它爲編譯器所定義(即生成並編譯函數體)。對於 union 類型,隱式定義的複製構造函數(如同以 std::memmove)複製其對象表示。對於非聯合類類型(class 與 struct),該構造函數用直接初始化,按照初始化順序,對對象的各基類和非靜態成員進行完整的逐成員複製。code
編譯器自動合成的 copy constructor 也是分爲 trivial 和 non-trivial 的對象
對於 trivial copy constructor 的條件,cppreference 中也給出了詳細的說明:繼承
當下列各項所有爲真時,類 T 的複製構造函數爲平凡的:
它不是用戶提供的(即它是隱式定義或預置的),且若它被預置,則其簽名與隱式定義的相同;
T 沒有虛成員函數;
T 沒有虛基類;
爲 T 的每一個直接基類選擇的複製構造函數都是平凡的;
爲 T 的每一個類類型(或類類型數組)的非靜態成員選擇的複製構造函數都是平凡的;遞歸
而在《深度探索 C++對象模型》中有一句話「決定一個copy constructor是否爲trivial的標準在於class是否展示出所謂的bitwise copy semantics」
;即若是一個 class 展示出了 bitwise copy semantics,那麼編譯器爲其合成的 copy constructor 就是 trivial 的
換言之,若是不知足 bitwise copy semantics,那麼編譯器合成的 copy constructor 就是 non-trivial 的。什麼時候一個 class 不表現出 bitwise copy semantics 呢?書中給了四個條件(略有修改):
其實這個四個條件至關於 cppreference 中提到的成爲 trivial copy constructor 的相反條件
關於 trivial copy constructor 的行爲,cppreference 也有提到:
非聯合類的平凡複製構造函數,效果爲複製實參的每一個標量子對象(遞歸地包含子對象的子對象,以此類推),且不進行其餘動做。不過不須要複製填充字節,甚至只要其值相同,每一個複製的子對象的對象表示也沒必要相同。
這句話的意思是說,若是編譯器合成的出來 copy constructor 是 trivial 的,它展示出這種行爲:逐個字節的拷貝全部內容
舉個例子:
class A { private: int _a; }; int main() { A a; A aa = a; return 0; }
其中 A aa = a;這一句,會調用編譯器產生的 trivial copy constructor,該 trivial copy constructor 會一個字節一個字節的把 a 中的成員變量的值拷貝到 aa 對應的成員變量中去
這彷佛看起來挺好的呀,也正是咱們所須要的結果,可是,若是 class A 中的成員變量是一根指針,那麼問題就大了:
#include <iostream> using namespace std; class A { public: int *p; }; int main() { A a; int val = 1; a.p = &val; A aa = a; cout << a.p << endl; cout << aa.p << endl; *(aa.p) = 2; cout << *(a.p) << endl; cout << *(aa.p) << endl; } // 上述程序的輸出爲 0x7ffc5d760414 0x7ffc5d760414 2 2
也就是說,在編譯器自動爲咱們合成的 trivial copy constructor 的行爲中,複製了 a 的指針給了 aa(淺拷貝),也就是說 a 和 aa 中的指針 p 指向了相同的地址!!!
在這種含有指針的狀況下,編譯器產生的 trivial copy constructor 的行爲便不是咱們所但願的,咱們必須手動顯示的定義一個符合咱們需求的 copy constructor 來完成對指針的拷貝
cppreference 中已經說了:
對於非聯合類類型(class 與 struct),該構造函數用直接初始化,按照初始化順序,對對象的各基類和非靜態成員進行完整的逐成員複製。
non-trivial copy constructor 一個很重要的行爲是:確保 vptr 的準確設定。(由於只要包含虛機制,那麼編譯器自動合成的 copy constructor 就不多是 trivial 的)
上面一點,書中已經說的足夠清楚,此文再也不贅述