在咱們沒有顯式定義類的複製構造函數和賦值操做符的狀況下,編譯器會爲咱們生成默認的這兩個函數:
默認的賦值函數之內存複製的形式完成對象的複製。
這種機制能夠爲咱們節省不少編寫複製構造函數和賦值操做符的時間,可是在某些狀況下,好比咱們不但願對象被複制,
在以前咱們須要將複製構造函數和賦值操做符聲明爲private,如今可使用delete關鍵字實現:html
class X { // … X& operator=(const X&) = delete; // 禁用類的賦值操做符 X(const X&) = delete; };
顯式地使用default關鍵字聲明使用類的默認行爲,對於編譯器來講明顯是多餘的,可是對於代碼的閱讀者來講,使用default顯式地定義複製操做,則意味着這個複製操做就是一個普通的默認的複製操做。git
派生類中能夠不實現基類虛函數,也能夠實現,但不使用virtual關鍵字;
這很容易給人形成混淆,有時爲了確認某個函數是不是虛函數,咱們不得不追溯到基類查看;
C++11引入了兩個新的標識符: override和final
override,表示函數應當重寫基類中的虛函數。(用於派生類的虛函數中)
final,表示派生類不該當重寫這個虛函數。(用於基類中)github
struct B { virtual void f(); virtual void g() const; virtual void h(char); void k(); // non-virtual virtual void m() final; }; struct D : B { void f() override; // OK: 重寫 B::f() void g() override; // error: 不一樣的函數聲明,不能重寫 virtual void h(char); // 重寫 B::h( char ); 可能會有警告 void k() override; // error: B::k() 不是虛函數 virtual void m(); // error: m()在基類中聲明禁止重寫 };
有了這對兄弟,咱們的虛函數用起來更爲安全,也更好閱讀;安全
在C++98中,若是你想讓兩個構造函數完成類似的事情,能夠寫兩個大段代碼相同的構造函數,或者是另外定義一個init()函數,讓兩個構造函數都調用這個init()函數。例如:ide
class X { int a; // 實現一個初始化函數 validate(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); } public: // 三個構造函數都調用validate(),完成初始化工做 X(int x) { validate(x); } X() { validate(42); } X(string s) { int x = lexical_cast<int>(s); validate(x); } // … };
這樣的實現方式重複羅嗦,而且容易出錯。
在C++0x中,咱們能夠在定義一個構造函數時調用另一個構造函數:函數
class X { int a; public: X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); } // 構造函數X()調用構造函數X(int x) X() :X{42} { } // 構造函數X(string s)調用構造函數X(int x) X(string s) :X{lexical_cast<int>(s)} { } // … };
C++11提供了將構造函數晉級的能力:
好比如下這個示例,基類提供一個帶參數的構造函數,而派生類沒有提供;
若是直接使用D1 d(6);將會報錯;經過將基類構造函數晉級,派生類中會隱式聲明構造函數D1(int);
須要注意的是,晉級後的基類構造函數是沒法初始化派生類的成員變量的,因此若是派生類中有成員變量,
須要使用初始化列表初始化;加密
struct B1 { B1(int) { } }; struct D1 : B1 { using B1::B1; // 隱式聲明構造函數D1(int) // 注意:在聲明的時候x變量已經被初始化 int x{0}; }; void test() { D1 d(6); // d.x的值是0 }
在C++98標準裏,只有static const聲明的整型成員能在類內部初始化,而且初始化值必須是常量表達式。這些限制確保了初始化操做能夠在編譯時期進行。.net
class X { static const int m1 = 7; // 正確 const int m2 = 7; // 錯誤:無static static int m3 = 7; // 錯誤:無const static const string m5 = 「odd」; //錯誤:非整型 };
C++11的基本思想是,容許非靜態(non-static)數據成員在其聲明處(在其所屬類內部)進行初始化。這樣,在運行時,須要初始值時構造函數可使用這個初始值。如今,咱們能夠這麼寫:code
class A { public: int a = 7; }; 它等同於使用初始化列表: class A { public: int a; A() : a(7) {} };
單純從代碼來看,這樣只是省去了一些文字輸入,但在有多個構造函數的類中,其好處就很明顯了:htm
class A { public: A(): a(7), b(5), hash_algorithm(「MD5″), s(「Constructor run」) {} A(int a_val) : a(a_val), b(5), hash_algorithm(「MD5″), s(「Constructor run」) {} A(D d) : a(7), b(g(d)), hash_algorithm(「MD5″), s(「Constructor run」) {} int a, b; private: // 哈希加密函數可應用於類A的全部實例 HashingFunction hash_algorithm; std::string s; // 用以指明對象正處於生命週期內何種狀態的字符串 };
能夠簡化爲:
class A { public: A() {} A(int a_val) : a(a_val) {} A(D d) : b(g(d)) {} int a = 7; int b = 5; private: //哈希加密函數可應用於類A的全部實例 HashingFunction hash_algorithm{「MD5″}; //用以指明對象正處於生命週期內何種狀態的字符串 std::string s{「Constructor run」};
多麼優雅!
在C++98中,咱們自定義的類,會默認生成拷貝賦值操做符函數和拷貝賦值函數以及析構函數;
在C++11中,依賴於新增的move語義,默認生成的函數多了2個移動相關的:移動賦值操做符( move assignment )和移動構造函數( move constructor );
BS建議,若是你顯式聲明瞭上述 5 個函數或操做符中的任何一個,你必須考慮其他的 4 個,而且顯式地定義你須要的操做,或者使用這個操做的默認行爲。
一旦咱們顯式地指明( 聲明 , 定義 , =default , 或者 =delete )了上述五個函數之中的任意一個,編譯器將不會默認自動生成move操做。
一旦咱們顯式地指明( 聲明 , 定義 , =default , 或者 =delete )了上述五個函數之中的任意一個,編譯器將默認自動生成全部的拷貝操做。可是,咱們應該儘可能避免這種狀況的發生,不要依賴於編譯器的默認動做。
若是你聲明瞭上述 5 個默認函數中的任何一個,強烈建議你顯式地聲明全部這 5 個默認函數。例如:
template<class T> class Handle { T* p; public: Handle(T* pp) : p{pp} {} // 用戶定義構造函數: 沒有隱式的拷貝和移動操做 ~Handle() { delete p; } Handle(Handle&& h) :p{h.p} { h.p=nullptr; }; // transfer ownership Handle& operator=(Handle&& h) { delete p; p=h.p; h.p=nullptr; } // 傳遞全部權 Handle(const Handle&) = delete; // 禁用拷貝構造函數 Handle& operator=(const Handle&) = delete; // ... };
http://www.stroustrup.com/C++11FAQ.html
https://www.chenlq.net/books/cpp11-faq
Posted by: 大CC | 02SEP,2015
博客:blog.me115.com [訂閱]
Github:大CC