一. 特殊成員函數ios
(一)概述編程
1. 特殊成員函數指C++會自行生成的成員函數,主要有6種:默認構造函數、析構函數、複製構造函數、複製賦值函數、移動構造函數和移動賦值函數。函數
2. 默認生成的特殊成員函數都具備public訪問權限且是inline的非虛函數(除析構例外)。一般,若是這些函數不被相關代碼使用,編譯器不會爲其產生真正的函數代碼。this
3. 若是基類析構函數是虛函數,則編譯器爲派生類生成的析構函數也是個虛函數。spa
4. 默認的複製或移動都是指對非靜態成員的操做,它們都是「按成員複製」或「按成員移動」的。而「按成員移動」實際上更像是按成員移動的請求,由於有些類型不具有移動操做,對於這些對象會經過其複製操做來實現「移動」。日誌
(二)C++11中的生成機制code
1. 默認構造函數:僅當類中不包含用戶聲明的構造函數時(含無參/帶參構造函數、複製構造和移動構造)才生成。對象
2. 析構函數:僅當基類的析構函數爲virtual時,派生類的析構函數纔是虛的。其與C++98的機制基本相同。惟一區別在於析構函數默認爲noexcept。blog
3. 複製構造函數:生成條件(① 該類未聲明覆制構造;②該類未聲明移動操做;③該類未聲明覆制賦值或析構函數)。注意,在當前聲明覆制賦值或析構函數時,仍會生成默認的複製構造函數,但該行爲會被逐漸廢棄。資源
4. 複製賦值函數:生成條件(① 該類未聲明覆制賦值;②該類未聲明移動操做;③該類未聲明覆制構造或析構函數)。注意,在當前聲明覆制構造或析構函數時,仍會生成默認的複製賦值函數,但該行爲會被逐漸廢棄。
5. 移動構造和移動賦值函數:僅當類中不包含用戶聲明的複製操做、移動操做和析構函數等3個條件知足時才生成。
2、理解生成機制
(一)生成機制背後的思想
1. 複製操做是彼此獨立的,即聲明瞭其中一個,並不會阻止編譯器生成另外一個。
早期編譯器認爲默認的複製構造和複製賦值都是按成員複製的。所以,它仍願意提供這種最簡單的「淺拷貝」方式以供調用(如遇到成員變量是引用或常量時,就能夠有機會提示報錯信息)。而若是須要進行「深拷貝」則須由用戶自行定義相應的函數。
2. 移動操做並不彼此獨立,即聲明瞭其中一個,就會阻止編譯器生成另外一個。
假設若是聲明一個移動構造函數,實際上表示移動操做與編譯器生成的按默認「按成員移動」是不一樣的。而若是「按成員移動」操做不符合要求時,按成員進行的移動賦值也極及可能不符合要求。
3. 複製操做與移動操做會相互影響
①若是聲明覆制操做(不管是複製構造或複製賦值)的行爲,這代表對象按成員複製不符合要求。編譯器認爲,既然按成員複製不符合要求,那麼「按成員移動」也很可能不符合要求。
②反之,若是聲明瞭移動操做(移動構造或移動賦值),就表示「按成員移動」不符合要求,也就沒理由指望按成員複製符合要求。
(二)大三律及推論
1. 大三律(Rule of Three):若是聲明瞭複製構造、複製賦值或析構函數中的任何一個,就得同時聲明全部這三個。
①在一種複製操做中進行資源管理,也極有可能在另外一種複製操做中也須要進行。
②該類的析構函數也會參與到該資源的管理中(釋放之)。
2. 推論
①若是聲明的析構函數,則平凡的按成員複製不適於該類。即複製操做不會被自動生成,由於它們的行爲都是不正確的。但在C++11中仍保留生成複製操做函數,僅僅是出於兼容C++98的遺留代碼而己,這種行爲將逐漸被廢棄。
②若是聲明瞭析構函數,就不會生成移動操做(由於析構函數會抑制複製操做,而複製操做又會阻止生成移動操做)。
(三)注意事項
1. 爲了消除因相互抑制而產生的依賴關係,在C++11中能夠經過「=default」來顯式指示編譯器生成默認版本的特殊成員函數。
2. 成員函數模板在任何狀況下都不會抑制特殊成員函數的生成。
3. 在己經顯式聲明析構函數時,會阻止生成移動操做,而但仍會生成複製操做(將逐漸被廢棄)。這可能會產生反作用,即原來移動操做可能會變成複製操做。
4. 根據大三律原則,當聲明析構函數時,通常應同時提供複製操做和移動操做函數。
【編程實驗】特殊成員函數生成機制
#include <iostream> #include <map> using namespace std; //1. 演示特殊成員函數之間的抑制關係 //1.1 自定義析構函數會阻止默認移動操做(含移動構造和移動賦值) class Foo { private: int i; public: Foo() = default; Foo(const Foo& f) = delete; //刪除複製構造函數!讓移動操做只會去找移動操做的函數,而不是被複製取代! Foo& operator=(const Foo&) = delete; //理由同上! ~Foo() {}; //這裏自定義析構函數! }; //1.2 自定義析構函數使「移動操做」變爲「複製操做」! class StringTable { std::map<int, std::string> values; void makeLogEntry(std::string s){}; //日誌記錄 public: StringTable() { makeLogEntry("Creating StringTable Object"); } ~StringTable() //注意,這裏自定義了析構函數,會產生反作用! { makeLogEntry("Destroying StringTable Object"); } }; //2. 大三律 class Bar { public: virtual ~Bar() = default; //自定義了析構函數,則應同時提供複製和移動操做! Bar(Bar&&) = default; //提供移動操做的支持 Bar& operator=(Bar&&) = default; Bar(const Bar&) = default;//提供移動操做的支持 Bar& operator=(const Bar&) = default; }; //3. 特種成員函數模板 class Widget { public: template<typename T> Widget(const T& rhs) { cout <<"Widget(const T& rhs)" << endl; } template<typename T> Widget& operator=(const T& rhs) { cout << "Widget& operator=(const T& rhs)" << endl; return *this; } public: Widget() = default; }; int main() { //1. 自定義析構函數,會阻止移動操做 Foo f; //Foo f1 = std::move(f); //編譯失敗! 因爲自定義了析構函數,默認移動操做被刪除。 //f1 = std::move(w); //編譯失敗! 緣由同上。 StringTable st1; StringTable st2 = std::move(st1); //這裏本意是調用移動構造函數,但因該類的析構函數阻止生成 //默認的移動操做,轉而變爲複製操做。而對於values對象的復 //制,而td::map<int, std::string>的複製每每比移動慢不少! //3. 特種成員函數模板不會抑制編譯器生成默認的特殊成員函數 Widget w; Widget w1 = w; //調用編譯器生成的默認複製構造函數 Widget w2 = 2; //調用函數模板:Widget(const T& rhs) w1 = w2; //調用編譯器生成的默認複製賦值函數 w1 = 2; //調用函數模板:Widget& operator=(const T& rhs) return 0; } /*輸出結果 Widget(const T& rhs) Widget& operator=(const T& rhs) */