【深刻理解C++11【2】】ios
一、繼承構造函數。c++
當基類擁有多個構造函數的時候,子類不得不一一實現。程序員
C++98 可使用 using 來使用基類的成員函數。數組
#include < iostream> using namespace std; struct Base { void f( double i){ cout << "Base:" << i << endl; } }; struct Derived : Base { using Base:: f; void f( int i) { cout << "Derived:" << i << endl; } }; int main() { Base b; b. f( 4. 5); // Base: 4. 5 Derived d; d. f( 4. 5); // Base: 4. 5 } // 編譯 選項: g++ 3- 1- 3. cpp
C++11中,這個功能由成員函數擴展到了構造函數上。ide
struct A { A( int i) {} A( double d, int i) {} A( float f, int i, const char* c) {} // ... }; struct B : A { using A:: A; // 繼承 構造 函數 // ... virtual void ExtraInterface(){} };
這 意味着 若是 一個 繼承 構造 函數 不被 相關 代碼 使用, 編譯器 不 會爲 其 產生 真正 的 函數 代碼。函數
對於 繼承 構造 函數 來說, 參數 的 默認值 是 不會被繼承 的。 事實上, 默認值 會 致使 基 類 產生 多個 構造 函數 的 版本, 這些 函數 版本 都會 被 派生 類 繼承。性能
若是 一旦 使用 了 繼承 構造 函數, 編譯器 就不 會 再爲 派生 類 生成 默認 構造 函數 了,優化
struct A { A (int){} }; struct B : A{ using A:: A; }; B b; // B 沒有 默認 構造 函數
截至2013年,尚未編譯器實現了繼承構造函數。this
二、委派構造函數。spa
C++98 中編譯器 不容許 在 構造 函數 中 調用 構造 函數, 即便 參數 看起來 並不 相同。如下代碼會產生編譯錯誤。
Info() { InitRest(); }
Info( int i) { this-> Info(); type = i; }
Info( char e) { this-> Info(); name = e; }
因此C++98的hacker喜歡使用 placement new 來實現調用其它構造函數。
Info() { InitRest(); } Info( int i) { new (this) Info(); type = i; } Info( char e) { new (this) Info(); name = e; }
C++11 中可使用委派構造函數來簡化代碼。
class Info { public: Info() { InitRest(); } Info( int i) : Info() { type = i; } Info( char e): Info() { name = e; } private: void InitRest() { /* 其餘 初始化 */ } int type {1}; char name {'a'}; // ... }; // 編譯 選項: g++ -c -std= c++ 11 3- 2- 3. cpp
調用「 基準 版本」 的 構造 函數 爲 委派 構造 函數( delegating constructor), 而被 調用 的「 基準 版本」 則爲 目標 構造 函數( target constructor)。
構造 函數 不能 同時「 委派」 和 使用 初始化 列表, 因此 若是 委派 構造 函數 要給 變量 賦 初值, 初始化 代碼 必須 放在 函數 體中。以下:
struct Rule1 { int i; Rule1( int a): i( a) {} Rule1(): Rule1( 40), i( 1) {} // 沒法 經過 編譯 };
目標 構造 函數 的 執行 老是 先於 委派 構造 函數 而 形成 的。
委派 構造 的 一個 很 實際 的 應用 就是 使用 構造模板函數 產生 目標 構造 函數,
class TDConstructed { template< class T> TDConstructed( T first, T last) : l( first, last) {} list< int> l; public: TDConstructed( vector< short> & v): TDConstructed( v. begin(), v. end()) {} TDConstructed( deque< int> & d): TDConstructed( d. begin(), d. end()) {} }; // 編譯 選項: g++ -c -std= c++ 11 3- 2- 6. cpp
委託構造函數的異常捕獲稍稍有些另類,try-catch 是在函數外:
class DCExcept { public: DCExcept( double d) try : DCExcept( 1, d) { cout << "Run the body." << endl; // 其餘 初始化 } catch(...) { cout << "caught exception." << endl; }
三、移動語義
C++98 中的經典問題,會產生多達分別3次的construct、destruct:
HasPtrMem GetTemp() { return HasPtrMem(); } int main() { HasPtrMem a = GetTemp(); } // 編譯 選項: g++ 3- 3- 3. cpp -fno- elide- constructors
輸出以下:
Construct: 1 Copy construct: 1 Destruct: 1 Copy construct: 2 Destruct: 2 Destruct: 3
移動構造函數與 Copy構造函數:
HasPtrMem( const HasPtrMem & h): d( new int(* h. d)) { cout << "Copy construct: " << ++ n_ cptr << endl; } HasPtrMem( HasPtrMem && h): d( h. d) { // 移動 構造 函數 h. d = nullptr; // 將臨 時值 的 指針 成員 置 空 cout << "Move construct: " << ++ n_ mvtr << endl; }
Construct: 1 Resource from GetTemp: 0x603010 Move construct: 1 Destruct: 1 Move construct: 2 Destruct: 2 Resource from main: 0x603010 Destruct: 3
四、左值、右值、右值引用
1)等號左邊的是「左值」。等號右邊的是「右值」。
2)能夠取地址的、有名字是左值。不能取地址、沒有名字的就是右值。
C++11程序中,全部值必屬於左值、將亡值、右值。
右值引用是不能綁定到任何左值的,以下代碼就沒法經過編譯:
int c; int && d = c;
常量左值引用能夠接受右值,同時也像右值引用同樣將右值生命週期延長。以下第一行:
const bool & judgement = true; const bool judgement = true;
C++98中也常可使用常量左值引用來減小臨時對象的開銷。
#include < iostream> using namespace std; struct Copyable { Copyable() {} Copyable( const Copyable &o) { cout << "Copied" << endl; } }; Copyable ReturnRvalue() { return Copyable(); } void AcceptVal( Copyable) {} void AcceptRef( const Copyable & ) {} int main() { cout << "Pass by value: " << endl; AcceptVal( ReturnRvalue()); // 臨 時值 被 拷貝 傳入 cout << "Pass by reference: " << endl; AcceptRef( ReturnRvalue()); // 臨 時值 被 做爲 引用 傳遞 } // 編譯 選項: g++ 3- 3- 5. cpp -fno- elide- constructors
Pass by value:
Copied
Copied
Pass by reference:
Copied
std::move 的做用是強制一個左值成爲右值。
由於 const T& 是萬能引用,能夠指向右值。因此當類實例無 MoveConstructor時,將會調用 CopyConstructor來替代。
但 const T&& 常量右值引用沒有卵用,一來MoveConstructor須要引用右值;二來若是右值不能改,使用 const T& 就能夠了。因此 const T&&沒有卵用。
爲了知道一個類型是不是引用類型,可使用 <type_traits>頭文件中的3個模板類:
1)is_rvalue_reference
2)is_lvalue_reference
3)is_reference
五、std::move
std::move並不能移動任何東西,它惟一的功能是將一個左值強制轉化爲右值引用。std::move 基本贊同於一個類型轉換:
static_ cast< T&&>( lvalue);
使用 std::move 時,咱們但願轉換爲右值引用的這個值是一個生命期即將結束的對象。一個正確的 std::move 的使用場景。
class HugeMem{ public: HugeMem( int size): sz( size > 0 ? size : 1) { c = new int[ sz]; } ~ HugeMem() { delete [] c; } HugeMem( HugeMem && hm): sz( hm. sz), c( hm. c) { hm. c = nullptr; } int * c; int sz; }; class Moveable{ public: Moveable(): i( new int( 3)), h( 1024) {} ~ Moveable() { delete i; } Moveable( Moveable && m): i( m. i), h( move( m. h)) { // 強制 轉爲 右 值, 以 調用 移動 構造 函數 m. i = nullptr; } int* i; HugeMem h; };
六、移動語義的其它問題。
下述代碼會使得 的 臨時 變量 常 量化, 成爲 一個 常量 右 值, 那麼 臨時 變量 的 引用 也就 沒法 修改, 從而 致使 沒法 實現 移動 語義。
Moveable( const Moveable &&) const Moveable ReturnVal();
編譯器 會爲 程序員 隱式 地 生成 一個( 隱式 表示 若是 不被 使用 則 不 生成) 移動 構造 函數。 不過 若是 程序員 聲明 了 自定義 的 拷貝 構造 函數、 拷貝 賦值 函數、 移動 賦值 函數、 析 構 函數 中的 一個 或者 多個, 編譯器 都不 會 再爲 程序員 生成 默認 版本。
聲明 了 移動 構造 函數、 移動 賦值 函數、 拷貝 賦值 函數 和 析 構 函數 中的 一個 或者 多個, 編譯器 也不 會 再爲 程序員 生成 默認 的 拷貝 構造 函數。 因此 在 C++ 11 中, 拷貝 構造/ 賦值 和 移動 構造/ 賦值 函數 必須 同時 提供, 或者 同時 不 提供, 程序員 才能 保證 類同 時 具備 拷貝 和 移動 語義。
只有 移動 語義 的 類型 則 很是 有趣, 由於 只有 移動 語義 代表 該 類型 的 變量 所 擁有 的 資源 只能 被 移動, 而 不能 被 拷貝。 那麼 這樣 的 資源 必須 是 惟一 的。 所以, 只有 移動 語義 構造 的 類型 每每 都是「 資源 型」 的 類型, 好比說 智能 指針, 文件 流 等,
有了移動語義,能夠實現高性能的 swap 函數。
template < class T> void swap( T& a, T& b) { T tmp( move( a)); a = move( b); b = move( tmp); }
程序員 應該 儘可能 編寫 不 拋出 異常 的 移動 構造 函數, 經過 爲 其 添加 一個 noexcept 關鍵字, 能夠 保證 移動 構造 函數 中 拋出 來的 異常 會 直接 調用 terminate 程序 終止 運行, 而 不是 形成 指針 懸掛 的 狀態。
一個 std:: move_ if_ noexcept 的 模板 函數 替代 move 函數。 該 函數 在 類 的 移動 構造 函數 沒有 noexcept 關鍵字 修飾 時 返回 一個 左 值 引用 從而 使 變量 能夠 使用 拷貝 語義, 而在 類 的 移動 構造 函數 有 noexcept 關鍵字 時, 返回 一個 右 值 引用, 從而 使 變量 能夠 使用 移動 語義。以下:
struct Maythrow { Maythrow() {} Maythrow( const Maythrow&) { std:: cout << "Maythorow copy constructor." << endl; } Maythrow( Maythrow&&) { std:: cout << "Maythorow move constructor." << endl; } }; struct Nothrow { Nothrow() {} Nothrow( Nothrow&&) noexcept { std:: cout << "Nothorow move constructor." << endl; } Nothrow( const Nothrow&) { std:: cout << "Nothorow move constructor." << endl; } }; int main() { Maythrow m; Nothrow n; Maythrow mt = move_ if_ noexcept( m); // Maythorow copy constructor. Nothrow nt = move_ if_ noexcept( n); // Nothorow move constructor. return 0; } // 編譯 選項: g++ -std= c++ 11 3- 3- 8. cpp
在 本節 中 大量 的 代碼 都 使用 了- fno- elide- constructors 選項 在 g++/ clang++ 中 關閉 這個 優化。若是關閉,則下述代碼只會有一次 constructor、destructor:
A ReturnRvalue() { A a(); return a; } A b = ReturnRvalue();
七、完美轉發
完美 轉發( perfect forwarding), 是指 在 函數 模板 中, 徹底 依照 模板 的 參數 的 類型, 將 參數 傳遞 給 函數 模板 中 調用 的 另外 一個 函數。下面的例子中,若是傳入的是一個右值,t被傳給內部函數時成了一個左值,不是完美轉發。
template < typename T> void IamForwording( T t) { IrunCodeActually( t); }
咱們但願傳入左值,傳出也是左值;傳入右值,傳出也是右值。
C++11加入了引用摺疊新規則。在C++98中,如下代碼會致使編譯錯誤。
typedef const int T; typedef T& TR; TR& v = 1; // 該 聲明 在 C++ 98 中會 致使 編譯 錯誤
模板類型 的 推導 規則 就比 較 簡單, 當 轉發 函數 的 實 參 是 類型 X 的 一個 左 值 引用, 那麼 模板 參數 被 推導 爲 X& 類型, 而 轉發 函數 的 實 參 是 類型 X 的 一個 右 值 引 用的 話, 那麼 模板 的 參數 被 推導 爲 X&& 類型。
template < typename T> void IamForwording( T && t) { IrunCodeActually( static_ cast< T &&>(t)); }
上面就是完美轉發。
八、顯式類型轉換
只有一個參數的構造函數也定義了一個隱式轉換,將該構造函數對應數據類型的數據轉換爲該類對象。
class String { public: String ( const char* p ); // 用C風格的字符串p做爲初始化值 //… } String s1 = 「hello」; //OK 隱式轉換,等價於String s1 = String(」hello」)
在 C++ 11 中, 標準 將 explicit 的 使用範圍 擴展到 了 自定義 的 類型 轉換 操做 符 上。
class ConvertTo {}; class Convertable { public: explicit operator ConvertTo () const { return ConvertTo(); } }; void Func( ConvertTo ct) {} void test() { Convertable c; ConvertTo ct( c); // 直接 初始化, 經過 ConvertTo ct2 = c; // 拷貝 構造 初始化, 編譯 失敗 ConvertTo ct3 = static_ cast< ConvertTo>( c); // 強制 轉化, 經過 Func( c); // 拷貝 構造 初始化, 編譯 失敗 } // 編譯 選項: g++ -std= c++ 11 3- 4- 3. cpp
九、初始化列表
C++98 中只容許對數組進行列表初始化,C++11擴展到了全部元素。
#include < vector> #include < map> using namespace std; int a[] = {1, 3, 5}; // C++ 98 經過, C++ 11 經過 int b[] {2, 4, 6}; // C++ 98 失敗, C++ 11 經過 vector< int> c{ 1, 3, 5}; // C++ 98 失敗, C++ 11 經過 map< int, float> d = {{1, 1. 0f}, {2, 2. 0f} , {5, 3. 2f}}; // C++ 98 失敗, C++ 11 經過 // 編譯 選項: g++ -c -std= c++ 11 3- 5- 1. cpp
如上,列表 初始化 能夠 在「{}」 花 括號 以前 使用 等號, 其 效果 與 不帶 使用 等號 的 初始化 相同。
{}和()同樣,也能夠用於 new操做符中。
int * i = new int( 1); double * d = new double{ 1. 2f};
自定義類型如須要初始化列表功能,須要提供一個構造函數,此函數使用惟一一個參數,initialize_list。
enum Gender {boy, girl}; class People { public: People( initializer_ list< pair< string, Gender>> l) { // initializer_ list 的 構造 函數 auto i = l. begin(); for (;i != l. end(); ++ i) data. push_ back(* i); } private: vector< pair< string, Gender>> data; }; People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}}; // 編譯 選項: g++ -c -std= c++ 11 3- 5- 2. cpp
函數也可使用初始化列表:
void Fun( initializer_ list< int> iv){ } int main() { Fun({ 1, 2}); Fun({}); // 空 列表 } // 編譯 選項: g++ -std= c++ 11 3- 5- 3. cpp
當初始化列表 + operator[] + operator= 時,會產生強大的語法效果:
using namespace std; class Mydata { public: Mydata & operator [] (initializer_ list< int> l) { for (auto i = l. begin(); i != l. end(); ++ i) idx. push_ back(* i); return *this; } Mydata & operator = (int v) { if (idx. empty() != true) { for (auto i = idx. begin(); i != idx. end(); ++ i) { d. resize((* i > d. size()) ? *i : d. size()); d[* i - 1] = v; } idx. clear(); } return *this; } void Print() { for (auto i = d. begin(); i != d. end(); ++ i) cout << *i << " "; cout << endl; } private: vector< int> idx; // 輔助 數組, 用於 記錄 index vector< int> d; }; int main() { Mydata d; d[{ 2, 3, 5}] = 7; d[{ 1, 4, 5, 8}] = 4; d. Print(); // 4 7 7 4 4 0 0 4 } // 編譯 選項: g++ -std= c++ 11 3- 5- 4. cpp
初始化 列表 還能夠 用於 函數 返回 的 狀況。 返回 一個 初始化 列表, 一般 會 致使 構造 一個 臨時 變量,\
vector< int> Func() { return {1, 3}; }
十、類型收窄
在 C++ 11 中, 使用 初始化 列表 進行 初始化 的 數據 編譯器 是 會 檢查 其是 否 發生 類型 收 窄 的。
const int x = 1024; const int y = 10; char a = x; // 收 窄, 但能夠 經過 編譯 char* b = new char( 1024); // 收 窄, 但能夠 經過 編譯 char c = {x}; // 收 窄, 沒法 經過 編譯 char d = {y}; // 能夠 經過 編譯 unsigned char e {-1}; // 收 窄, 沒法 經過 編譯 float f { 7 }; // 能夠 經過 編譯 int g { 2. 0f }; // 收 窄, 沒法 經過 編譯 float * h = new float{ 1e48}; // 收 窄, 沒法 經過 編譯 float i = 1. 2l; // 能夠 經過 編譯 // 編譯 選項: clang++ -std= c++ 11 3- 5- 5. cpp
在 C++ 11 中, 列表 初始化 是 惟一 一種 能夠 防止 類型 收 窄 的 初始化 方式。 這也 是 列表 初始化 區別於 其餘 初始化 方式 的 地方。
十一、
十二、
1三、