【深刻理解C++11【3】】ios
一、POD類型c++
Plain Old Data. Plain 表示 了POD是個普通的類型。C++11將POD劃分爲兩個基本概念的合集:程序員
1)平凡的(trivial)面試
2)標準佈局的(standard layout)express
一個平凡的類或結構體應該符合如下定義:數組
1)擁有平凡的默認構造函數(trivial constructor)和析構函數(trivial destructor)。安全
平凡 的 默認 構造 函數 就是說 構造 函數「 什麼 都 不幹」。 一般 狀況下, 不 定義 類 的 構造 函數, 編譯器 就會 爲我 們 生成 一個 平凡 的 默認 構造 函數。 而 一旦 定義 了 構造 函數, 即便 構造 函數 不 包含 參數, 函數 體 裏 也沒 有 任何 的 代碼, 那麼 該 構造 函數 也 再也不 是「 平凡」 的。ide
2)擁有平凡的拷貝構造函數(trivial copy constructor)和移動構造函數(trivial move constructor)。函數
3)擁有平凡的拷貝賦值運算符(trivial assignmnt operator)和移動賦值運算符(trivial move operator)。佈局
4)不能包含虛函數以及虛基類。
C++11中可使用 is_trivial struct來斷定一個類型是不是 trivial。
類 模板 is_ trivial 的 成員 value 能夠 用於 判斷 T 的 類型 是否 是一 個 平凡 的 類型。 除了 類 和 結構 體外, is_ trivial 還可 以對 內置 的 標量 類型 數據( 好比 int、 float 都 屬於 平凡 類型) 及 數組 類型( 元素 是 平凡 類型 的 數組 老是 平凡 的) 進行 判斷。
template < typename T> struct std:: is_ trivial;
標準佈局應該符合如下定義:
1)全部非靜態成員有相同的訪問權限。
2)在類或結構體繼承時,知足如下兩種狀況之一:
a)派生類中有非靜態成員,且只有一個僅包含靜態成員的基類。
b)基類有非靜態成員,而派生類沒有非靜態成員。
struct B1 { static int a; }; struct D1 : B1 { int d; }; struct B2 { int a; }; struct D2 : B2 { static int d; }; struct D3 : B2, B1 { static int d; }; struct D4 : B2 { int d; }; struct D5 : B2, D1 { };
D一、 D2 和 D3 都是 標準 佈局 的, 而 D4 和 D5 則 不屬於 標準 佈局 的。
非 靜態 成員 只要 同時 出現 在 派生 類 和 基 類 間, 其 即 不屬於 標準 佈局 的。 而 多重 繼承 也會 致使 類型 佈局 的 一些 變化, 因此 一旦 非 靜態 成員 出現 在 多個 基
3)類中第一個非靜態成員的類型與其基類不一樣。
struct A : B{ B b; }; // 不是標準佈局 struct C : B{int a; B b;} // 是標準佈局
4)沒有虛函數和虛基類。
5)全部 非 靜態 數據 成員 均 符合 標準 佈局 類型, 其 基 類 也 符合 標準 佈局。 這是 一個 遞歸 的 定義, 沒有 什麼 好 特別 解釋 的。
C++11中可使用struct模板 is_standard_layout 來判斷是不是標準佈局類型。
template < typename T> struct std:: is_ standard_ layout;
要斷定一個類型是不是 POD,可使用 is_pod模板:
template < typename T> struct std:: is_ pod;
使用 POD 有 什麼 好處 呢? 咱們 看 獲得 的 大概 有 以下 3 點:
1)字節賦值。memset、memcpy。
2)與C內存佈局兼容。
3)保證了靜態初始化的安全有效。
二、非受限聯合體
C++98中 union不能包含非POD類型。也不容許union擁有靜態或引用類型的成員。
在 新的 C++ 11 標準 中, 取消 了 聯合體 對於 數據 成員 類型 的 限制。 標準 規定, 任何 非 引用 類型 均可以 成爲 聯合體 的 數據 成員, 這樣 的 聯合體 即 所謂 的 非 受限 聯合體( Unrestricted Union)。
非 受限 聯合體 有一個 非 POD 的 成員, 而 該 非 POD 成員 類型 擁有 有 非 平凡 的 構造 函數, 那麼 非 受限 聯合體 成員 的 默認 構造 函數 將被 編譯器刪除。以下:
#include < string> using namespace std; union T { string s; // string 有 非 平凡 的 構造 函數 int n; }; int main() { T t; // 構造 失敗, 由於 T 的 構造 函數 被 刪除 了 } // 編譯 選項: g++ -std= c++ 11 3- 7- 4. cpp
解決 這個 問題 的 辦法 是, 由 程序員 本身 爲 非 受限 聯合體 定義 構造 函數。placement new將會發揮很好的做用。
#include < string> using namespace std; union T { string s; int n; public: // 自定義 構造 函數 和 析 構 函數 T(){ new (&s) string; } ~ T() { s.~ string(); } }; int main() { T t; // 構造 析 構成 功 } // 編譯 選項: g++ -std= c++ 11 3- 7- 5. cpp
匿名 非 受限 聯合體 能夠 運用於 類 的 聲明 中。
三、用戶自定義字面量 — 後綴標識操做符
struct RGBA{ uint8 r; uint8 g; uint8 b; uint8 a; RGBA( uint8 R, uint8 G, uint8 B, uint8 A = 0): r( R), g( G), b( B), a( A){} }; RGBA operator "" _C( const char* col, size_ t n) { // 一個 長度 爲 n 的 字符串 col const char* p = col; const char* end = col + n; const char* r, *g, *b, *a; r = g = b = a = nullptr; for(; p != end; ++ p) { if (*p == 'r') r = p; else if (*p == 'g') g = p; else if (*p == 'b') b = p; else if (*p == 'a') a = p; } if ((r == nullptr) || (g == nullptr) || (b == nullptr)) throw; else if (a == nullptr) return RGBA( atoi( r+ 1), atoi( g+ 1), atoi( b+ 1)); else return RGBA( atoi( r+ 1), atoi( g+ 1), atoi( b+ 1), atoi( b+ 1)); }
除去 字符串 外, 後綴 聲明 也能夠 做用於 數值, 好比, 用戶 可能 使用 60W、 120W 的 表示 方式 來 標識 功率, 用 50kg 來 表示 質量, 用 1200N 來 表示 力 等。
struct Watt{ unsigned int v; }; Watt operator "" _w( unsigned long long v) { return {(unsigned int) v}; } int main() { Watt capacity = 1024_ w; } // 編譯 選項: g++ -std= c++ 11 3- 8- 3. cpp
C++11中跟字面量「類型」密切相關的規則有如下四條:
1)若是 字面 量 爲 整型 數, 那麼 字面 量 操做 符 函數 只可 接受 unsigned long long 或者 const char* 爲 其 參數。
2)若是 字面 量 爲 浮點 型 數, 則 字面 量 操做 符 函數 只可 接受 long double 或者 const char* 爲 參數。
3)若是 字面 量 爲 字符串, 則 字面 量 操做 符 函數 函數 只可 接受 const char*, size_ t 爲 參數( 已知 長度 的 字符串)。
4)若是 字面 量 爲 字符, 則 字面 量 操做 符 函數 只可 接受 一個 char 爲 參數。
另外有如下幾點要注意:
1)operator"" 與 用戶 自定義 後綴 之間 必須 有 空格。
2)後綴 建議 如下 劃線 開始。 不宜 使用 非 下劃線 後綴 的 用戶 自定義 字符串 常量, 不然 會被 編譯器 警告。
四、內聯名字空間
namespace中的namespace會帶來一個問題,如parentspace::childspace,外部使用者調用childspace內的內容的,會比較麻煩,由於須要須要前綴 parentspace::childspace,是否能讓外部使用者不須要知道childspace的存在?
C++98 中能夠在parentspace中使用 using namespace child; 來解決,可是存在模板特化的問題。C++ 98 標準 不容許 在 不一樣 的 名字 空 間中 對 模板 進行 特 化。以下:
namespace Jim { namespace Basic { struct Knife{ Knife() { cout << "Knife in Basic." << endl; } }; class CorkScrew{}; } namespace Toolkit{ template< typename T> class SwissArmyKnife{}; } // ... namespace Other{ // ... } // 打開 一些 內部 名字 空間 using namespace Basic; using namespace Toolkit; } // LiLei 決定 對 該 class 進行 特 化 namespace Jim { template<> class SwissArmyKnife< Knife>{}; // 編譯 失敗 } using namespace Jim; int main() { SwissArmyKnife< Knife> sknife; } // 編譯 選項: g++ 3- 9- 2. cpp
在 C++ 11 中, 標準 引入 了 一個 新 特性, 叫作「 內聯的名字空間」。 經過 關鍵字「 inline namespace」 就能夠 聲明 一個 內聯 的 名字 空間。 內聯 的 名字 空間 容許 程序員 在 父名字空間 定義 或 特 化子名字空間 的 模板。
namespace Jim { inline namespace Basic { struct Knife{ Knife() { cout << "Knife in Basic." << endl; } }; class CorkScrew{}; } inline namespace Toolkit{ template< typename T> class SwissArmyKnife{}; } // ... namespace Other{ Knife b; // Knife in Basic struct Knife{ Knife() { cout << "Knife in Other" << endl;} }; Knife c; // Knife in Other Basic:: Knife k; // Knife in Basic } // 打開 一些 內部 名字 空間 using namespace Basic; using namespace Toolkit; } // LiLei 決定 對 該 class 進行 特 化 namespace Jim { template<> class SwissArmyKnife< Knife>{}; // 編譯 經過 } using namespace Jim; int main() { SwissArmyKnife< Knife> sknife; } // 編譯 選項: g++ 3- 9- 2. cpp
上面對 inline namespace 的用法是一種誤用,上述用法跟將 childspace中的內容放到 parentspace沒有區別。inline namespace正確的用法是用於版本管理。如:
namespace Jim { #if __cplusplus == 201103L inline #endif namespace cpp11{ struct Knife{ Knife() { cout << "Knife in c++ 11." << endl; } }; // ... } #if __cplusplus < 201103L inline #endif namespace oldcpp{ struct Knife{ Knife() { cout << "Knife in old c++." << endl; } }; // ... } } using namespace Jim; int main() { Knife a; // Knife in c++ 11. (默認 版本) cpp11:: Knife b; // Knife in c++ 11. (強制 使用 cpp11 版本) oldcpp:: Knife c; // Knife in old c++. (強制 使用 oldcpp11 版本) } // 編譯 選項: g++ -std= c++ 11 3- 9- 4. cpp
所謂「 參數 關聯 名稱 查找」, 即 ADL( Argument- Dependent name Lookup)。 這個 特性 容許 編譯器 在 名字 空 間內 找 不到 函數 名稱 的 時候, 在 參數 的 名字 空間 內 查找 函數 名字。
namespace ns_ adl{ struct A{}; void ADLFunc( A a){} // ADLFunc 定義 在 namespace ns_ adl 中 } int main() { ns_ adl:: A a; ADLFunc( a); // ADLFunc 無需 聲明 名字 空間 }
五、模板的別名。
C++98中只有typedef能夠定義類型的別名,C++11中using也能夠定義類型的別名。
#include < iostream> #include < type_ traits> using namespace std; using uint = unsigned int; typedef unsigned int UINT; using sint = int; int main() { cout << is_ same< uint, UINT>:: value << endl; // 1 } // 編譯 選項: g++ -std= c++ 11 3- 10- 1. cpp
不止於此,C++11中using 還能夠結合模板使用。
template< typename T> using MapString = std:: map< T, char*>; MapString< int> numberedString;
六、SFINEA,Substitution failure is not an error.
對 重載 的 模板 的 參數 進行 展開 的 時候, 若是 展開 致使 了 一些 類型 不匹配, 編譯器 並不 會 報錯。以下:
struct Test { typedef int foo; }; template < typename T> void f( typename T:: foo) {} // 第一個 模板 定義 - #1 template < typename T> void f( T) {} // 第二個 模板 定義 - #2 int main() { f< Test>( 10); // 調用# 1. f< int>( 10); // 調用# 2. 因爲 SFINEA, 雖然 不存在 類型 int:: foo, 也不 會 發生 編譯 錯誤 } // 編譯 選項: g++ 2- 14- 1. cpp
七、右尖括號的改進
在 C++ 98 中, 有 一條 須要 程序員 規避 的 規則: 若是 在 實例 化 模板 的 時候 出現 了 連續 的 兩個 右 尖 括號>, 那麼 它們 之間 須要 一個 空格 來 進行 分隔, 以 避免 發生 編譯 時 的 錯誤。
template < int i> class X{}; template < class T> class Y{}; Y< X< 1> > x1; // 編譯 成功 Y< X< 2>> x2; // 編譯 失敗 // 編譯 選項: g++ -c 4- 1- 1. cpp
上面代碼,在 x2 的 定義 中, 編譯器 會把>> 優先 解析 爲 右移 符號。除去上面這個例子,在使用 *_cast 時也會有一樣的問題。如:
const vector< int> v = static_cast< vector< int>>( v); //沒法 經過 編譯
C++11中,這種限制被取消了。
八、auto
int main() { double foo(); auto x = 1; // x 的 類型 爲 int auto y = foo(); // y 的 類型 爲 double struct m { int i; } str; auto str1 = str; // str1 的 類型 是 struct m auto z; // 沒法 推導, 沒法 經過 編譯 z = x; } // 編譯 選項: g++ -std= c++ 11 4- 2- 2. cpp
在類型自定義類型轉換情形中,auto 可以幫上大忙:
class PI { public: double operator* (float v) { return (double) val * v; // 這裏 精度 被 擴展 了 } const float val = 3. 1415927f; }; int main() { float radius = 1. 7e10; PI pi; auto circumference = 2 * (pi * radius); } // 編譯 選項: g++ -std= c++ 11 4- 2- 5. cpp
auto的另外一特性是自適應:
1)假設上述代碼 PI 的 做者 改動 了 PI 的 定義, 好比 將 operator* 返回 值 變爲 long double, 此時, main 函數 並不 須要 修改, 由於 auto 會「 自 適應」 新的 類型。
2)對於 不一樣 的 平 臺上 的 代碼 維護, auto 也會 帶來 一些「 泛 型」 的 好處。 這裏 咱們 以 strlen 函數 爲例, 在 32 位 的 編譯 環境 下, strlen 返回 的 爲 一個 4 字節 的 整型, 而在 64 位 的 編譯 環境 下, strlen 會 返回 一個 8 字節 的 整型。
3)當 auto 應用於 模板 的 定義 中, 其「 自 適應」 性 會 獲得 更加 充分 的 體現。以下:
template< typename T1, typename T2> double Sum( T1 & t1, T2 & t2) { auto s = t1 + t2; // s 的 類型 會在 模板 實例 化 時 被 推導 出來 return s; } int main() { int a = 3; long b = 5; float c = 1. 0f, d = 2. 3f; auto e = Sum< int ,long>( a, b); // s 的 類型 被 推導 爲 long auto f = Sum< float, float>( c, d); // s 的 類型 被 推導 爲 float } // 編譯 選項: g++ -std= c++ 11 4- 2- 7. cpp
auto 能夠提高性能:
#define Max1( a, b) ((a) > (b)) ? (a) : (b) #define Max2( a, b) ({ \ auto _a = (a); \ auto _b = (b); \ (_a > _b) ? _a: _b; }) int main() { int m1 = Max1( 1* 2* 3* 4, 5+ 6+ 7+ 8); int m2 = Max2( 1* 2* 3* 4, 5+ 6+ 7+ 8); } // 編譯 選項: g++ -std= c++ 11 4- 2- 8. cpp
前者 採用 傳統 的 三元 運算符 表達式, 這 可能 會 帶來 必定 的 性能 問題。 由於 a 或者 b 在 三元 運算符 中 都 出現 了 兩次,
九、auto的使用細則
實際上 對於 a、 c、 d 三個 變量 而言, 聲明 其爲 auto* 或 auto 並無 區別。 而 若是 要 使得 auto 聲明 的 變量 是 另外一個 變量 的 引用, 則 必須 使用 auto&。
int x; int * y = &x; double foo(); int & bar(); auto * a = &x; // int* auto & b = x; // int& auto c = y; // int* auto * d = y; // int* auto * e = &foo(); // 編譯 失敗, 指針 不能 指向 一個 臨時 變量 auto & f = foo(); // 編譯 失敗, nonconst 的 左 值 引用 不能 和 一個 臨時 變量 綁 定 auto g = bar(); // int auto & h = bar(); // int& // 編譯 選項: g++ -std= c++ 11 4- 2- 9. cpp
聲明 爲 auto 的 變量 並不能 從其 初始化 表達式 中「 帶走」 cv 限制 符。
double foo(); float * bar(); const auto a = foo(); // a: const double const auto & b = foo(); // b: const double& volatile auto * c = bar(); // c: volatile float* auto d = a; // d: double auto & e = a; // e: const double & auto f = c; // f: float * volatile auto & g = c; // g: volatile float * & // 編譯 選項: g++ -std= c++ 11 4- 2- 10. cpp
上述代碼中,經過 auto 聲明 的 變量 d、 f 卻 沒法 帶走 a 和 f 的 常量 性 或者 易 失 性。 這裏 的 例外 仍是 引用, 能夠 看出, 聲明 爲 引用 的 變量 e、 g 都 保持 了 其 引用 的 對象 相同 的 屬性(
用 auto 來 聲明 多個 變量 類型 時, 只有 第一個 變量 用於 auto 的 類型 推導。注意 const auto *:
auto x = 1, y = 2; // x 和 y 的 類型 均爲 int // m 是一 個 指向 const int 類型 變量 的 指針, n 是 一個 int 類型 的 變量 const auto* m = &x, n = 1; auto i = 1, j = 3. 14f; // 編譯 失敗 auto o = 1, &p = o, *q = &p; // 從左 向右 推導 // 編譯 選項: g++ -std= c++ 11 4- 2- 11. cpp -c
C++ 11 新 引入 的 初始化 列表, 以及 new, 均可以 使用 auto 關鍵字
#include < initializer_ list> auto x = 1; auto x1( 1); auto y {1}; // 使用 初始化 列表 的 auto auto z = new auto( 1); // 能夠 用於 new // 編譯 選項: g++ -std= c++ 11 -c 4- 2- 12. cpp
禁止使用 auto 的情形:
1)auto不能是函數形參。
2)非靜態成員變量的類型不能是auto。
3)不能聲明 auto 數組。
4)不能在模板參數中使用 auto。
#include < vector> using namespace std; void fun( auto x = 1){} // 1: auto 函數 參數, 沒法 經過 編譯 struct str{ auto var = 10; // 2: auto 非 靜態 成員 變量, 沒法 經過 編譯 }; int main() { char x[ 3]; auto y = x; auto z[ 3] = x; // 3: auto 數組, 沒法 經過 編譯 // 4: auto 模板 參數( 實例 化 時), 沒法 經過 編譯 vector< auto> v = {1}; } // 編譯 選項: g++ -std= c++ 11 4- 2- 13. cpp
十、typeid 與 decltype
C++ 98 對 動態 類型 支持 就是 C++ 中的 運行時 類型 識別( RTTI)。
RTTI 的 機制 是 爲 每一個 類型 產生 一個 type_info 類型的數據, 程序員 能夠 在 程序 中 使用 typeid 隨時 查詢 一個 變量 的 類型, typeid 就會 返回 變量 相應 的 type_info 數據。 而 type_info 的 name 成員 函數 能夠 返回 類型 的 名字。
而在 C++ 11 中, 又 增長 了 hash_code 這個成員函數, 返回該類型惟一的哈希值, 以供 程序員 對 變量 的 類型 隨時 進行 比較。
#include < iostream> #include < typeinfo> using namespace std; class White{}; class Black{}; int main() { White a; Black b; cout << typeid( a). name() << endl; // 5White cout << typeid( b). name() << endl; // 5Black White c; bool a_ b_ sametype = (typeid( a). hash_ code() == typeid( b). hash_ code()); bool a_ c_ sametype = (typeid( a). hash_ code() == typeid( c). hash_ code()); cout << "Same type? " << endl; cout << "A and B? " << (int) a_ b_ sametype << endl; // 0 cout << "A and C? " << (int) a_ c_ sametype << endl; // 1 } // 編譯 選項: g++ -std= c++ 11 4- 3- 1. cpp
上面代碼中,5 這樣 的 前綴 是 g++ 這類 編譯器 輸出 的 名字, 其餘 編譯器 可能 會 打印 出 其餘 的 名字, 這個 標準 並無 明確 規定),
因爲 RTTI 會 帶來 一些 運行時 的 開銷, 因此 一些 編譯器 會 讓 用戶 選擇 性地 關閉 該 特性( 好比 XL C/ C++ 編譯器 的- qnortti, GCC 的 選項- fno- rttion, 或者 微軟 編譯器 選項/ GR-)。
decltype 老是 以 一個 普通 的 表達式 爲 參數, 返回 該 表達式 的 類型。decltype的demo:
int main() { int i; decltype( i) j = 0; cout << typeid( j). name() << endl; // 打 印出" i", g++ 表示 int float a; double b; decltype( a + b) c; cout << typeid( c). name() << endl; // 打 印出" d", g++ 表示 double } // 編譯 選項: g++ -std= c++ 11 4- 3- 2. cpp
十一、decltype的應用
decltype 與 typedef/using 的全用:
using size_ t = decltype( sizeof( 0)); using ptrdiff_ t = decltype(( int*) 0 - (int*) 0); using nullptr_ t = decltype( nullptr);
decltype 能夠用於獲取匿名類型的類型。這是C++98所作不到的。
enum class{ K1, K2, K3} anon_ e; // 匿名 的 強 類型 枚舉 union { decltype( anon_ e) key; char* name; }anon_ u; // 匿名 的 union 聯合體 struct { int d; decltype( anon_ u) id; }anon_ s[ 100]; // 匿名 的 struct 數組 int main() { decltype( anon_ s) as; as[ 0]. id. key = decltype( anon_ e):: K1; // 引用 匿名 強 類型 枚舉 中的 值 } // 編譯 選項: g++ -std= c++ 11 4- 3- 4. cpp
與模板特化結合:
template< typename T1, typename T2> void Sum( T1 & t1, T2 & t2, decltype( t1 + t2) & s) { s = t1 + t2; } void Sum( int a[], int b[], int c[]) { // 數組 版本 } int main() { int a[ 5], b[ 5], c[ 5]; Sum( a, b, c); // 選擇 數組 版本 int d, e, f; Sum( d, e, f); // 選擇 模板 的 實例 化 版本 } // 編譯 選項: g++ -std= c++ 11 4- 3- 6. cpp
decltype 只能 接受 表達式 作 參數, 像 函數 名 作 參數 的 表達式 decltype( hash) 是 沒法 經過 編譯 的。
int hash( char*); map< char*, decltype( hash)> dict_ key; // 沒法 經過 編譯 map< char*, decltype( hash( nullptr))> dict_ key1;
std::result_of 能夠用於推導函數返回值類型:
typedef double (*func)(); int main() { result_ of< func()>:: type f; // 由 func() 推導 其 結果 類型 } // 編譯 選項: g++ -std= c++ 11 4- 3- 8. cpp
十一、decltype 推導四規則
decltype 能夠帶雙括號。以下:
int i; decltype( i) a; // a: int decltype(( i)) b; // b: int &, 沒法 編譯 經過
推導四規則以下:
1)最多見的情形,如前文所應用。若是 e 是一 個 沒有帶括號的標記符表達式( id- expression) 或者 類成員訪問表達式, 那麼 decltype( e) 就是 e 所 命名 的 實體 的 類型。 此外, 若是 e 是 一個 被重載的函數, 則 會 致使 編譯 時 錯誤。
2)不然, 假設 e 的 類型 是 T, 若是 e 是 一個 將 亡 值( xvalue), 那麼 decltype( e) 爲 T&&。
3)不然, 假設 e 的 類型 是 T, 若是 e 是 一個 左 值, 則 decltype( e) 爲 T&。
4)不然, 假設 e 的 類型 是 T, 則 decltype( e) 爲 T。
int i = 4; int arr[ 5] = {0}; int *ptr = arr; struct S { double d; } s; void Overloaded( int); void Overloaded( char); // 重載 的 函數 int && RvalRef(); const bool Func( int); // 規則 1: 單個 標記 符 表達式 以及 訪問 類 成員, 推導 爲本 類型 decltype( arr) var1; // int[ 5], 標記 符 表達式 decltype( ptr) var2; // int*, 標記 符 表達式 decltype( s. d) var4; // double, 成員 訪問 表達式 decltype( Overloaded) var5; // 沒法 經過 編譯, 是個 重載 的 函數 // 規則 2: 將 亡 值, 推導 爲 類型 的 右 值 引用 decltype( RvalRef()) var6 = 1; // int&& // 規則 3: 左 值, 推導 爲 類型 的 引用 decltype( true ? i : i) var7 = i; // int&, 三元 運算符, 這裏 返回 一個 i 的 左 值 decltype(( i)) var8 = i; // int&, 帶 圓括號 的 左 值 decltype(++ i) var9 = i; // int&, ++ i 返回 i 的 左 值 decltype( arr[ 3]) var10 = i; // int& []操做 返回 左 值 decltype(* ptr) var11 = i; // int& *操做 返回 左 值 decltype(" lval") var12 = "lval"; // const char(&)[ 9], 字符串 字面 常量 爲 左 值 // 規則 4: 以上 都不 是, 推導 爲本 類型 decltype( 1) var13; // int, 除 字符串 外 字面 常量 爲 右 值 decltype( i++) var14; // int, i++ 返回 右 值 decltype(( Func( 1))) var15; // const bool, 圓括號 能夠 忽略
可使用 is_rvalue_reference、is_lvalue_reference 來判斷是不是左右值引用。
#include < type_ traits> #include < iostream> using namespace std; int i = 4; int arr[ 5] = {0}; int *ptr = arr; int && RvalRef(); int main() { cout << is_ rvalue_ reference< decltype( RvalRef())>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( true ? i : i)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(( i))>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(++ i)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( arr[ 3])>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(* ptr)>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype(" lval")>:: value << endl; // 1 cout << is_ lvalue_ reference< decltype( i++)>:: value << endl; // 0 cout << is_ rvalue_ reference< decltype( i++)>:: value << endl; // 0 }
十二、cv限制符的繼承與冗餘符號
與 auto 類型 推導 時不 能「 帶走」 cv 限制 符 不一樣, decltype 是 可以「 帶走」 表達式 的 cv 限制 符 的。經過 is_const、is_volatile 能夠斷定 const、volatile 性。
using namespace std; const int ic = 0; volatile int iv; struct S { int i; }; const S a = {0}; volatile S b; volatile S* p = &b; int main() { cout << is_ const< decltype( ic)>:: value << endl; // 1 cout << is_ volatile< decltype( iv)>:: value << endl; // 1 cout << is_ const< decltype( a)>:: value << endl; // 1 cout << is_ volatile< decltype( b)>:: value << endl; // 1 cout << is_ const< decltype( a. i)>:: value << endl; // 0, 成員 不是 const cout << is_ volatile< decltype( p-> i)>:: value << endl; // 0, 成員 不是 volatile }
進行 類型 定義 時, 也會 容許 一些 冗餘 的 符號。 好比 cv 限制 符 以及 引用,符號&, 一般 狀況下, 若是 推 導出 的 類型 已經 有了 這些 屬性, 冗餘 的 符號 則 會被 忽略。
int i = 1; int & j = i; int * p = &i; const int k = 1; int main() { decltype( i) & var1 = i; decltype( j) & var2 = i; // 冗餘 的&, 被 忽略 cout << is_ lvalue_ reference< decltype( var1)>:: value << endl;// 1, 是 左 值 引用 cout << is_ rvalue_ reference< decltype( var2)>:: value << endl;// 0, 不是 右 值 引用 cout << is_ lvalue_ reference< decltype( var2)>:: value << endl;// 1, 是 左 值 引用 decltype( p)* var3 = &i; // 沒法 經過 編譯 decltype( p)* var3 = &p; // var3 的 類型 是 int** auto* v3 = p; // v3 的 類型 是 int* v3 = &i; const decltype( k) var4 = 1; // 冗餘 的 const, 被 忽略 }
這裏 特別 要 注意 的 是 decltype( p)* 的 狀況。 能夠 看到, 在 定義 var3 變量 的 時候, 因爲 p 的 類型 是 int*, 所以 var3 被 定義 爲了 int** 類型。 這 跟 auto 聲明 中,* 也能夠 是 冗餘 的 不一樣。 在 decltype 後的* 號, 並不 會被 編譯器 忽略。
1三、追蹤返回類型的引入
C++ 11 引入 新語 法— 追蹤 返回 類型。
template< typename T1, typename T2> auto Sum( T1 & t1, T2 & t2) -> decltype( t1 + t2) { return t1 + t2; }
咱們 把 函數 的 返回 值 移至 參數 聲明 以後, 複合 符號-> decltype( t1+ t2) 被稱爲 追蹤 返回 類型。 而 本來 函數 返回 值 的 位置 由 auto 關鍵字 佔據。 這樣, 咱們 就能夠 讓 編譯器 來 推導 Sum 函數 模板 的 返回 類型 了。 而 auto 佔位符 和-> return_ type 也就是 構成 追蹤 返回 類型 函數 的 兩個 基本 元素。
1四、使用追蹤返回類型的函數
使用追蹤返回類型的時候,能夠沒必要寫明做用域。以下,InnerType沒必要寫明其做用域。
class OuterType { struct InnerType { int i; }; InnerType GetInner(); InnerType it; }; // 能夠 不 寫 OuterType:: InnerType auto OuterType:: GetInner() -> InnerType { return it; }
返回 類型 後置, 使 模板 中的 一些 類型 推導 就成 爲了 可能。以下:
template< typename T1, typename T2> auto Sum( const T1 & t1, const T2 & t2) -> decltype( t1 + t2) { return t1 + t2; } template < typename T1, typename T2> auto Mul( const T1 & t1, const T2 & t2) -> decltype( t1 * t2) { return t1 * t2; } int main() { auto a = 3; auto b = 4L; auto pi = 3. 14; auto c = Mul( Sum( a, b), pi); cout << c << endl; // 21. 98 }
追蹤 返回 類型 的 另外一個 優點 是 簡化 函數 的 定義, 提升 代碼 的 可讀性。
#include < type_ traits> #include < iostream> using namespace std; // 有的 時候, 你會 發現 這是 面試 題 int (*(*pf())())() { return nullptr; } // auto (*)() -> int(*) () 一個 返回 函數 指針 的 函數( 假設 爲 a 函數) // auto pf1() -> auto (*)() -> int (*)() 一個 返回 a 函數 的 指針 的 函數 auto pf1() -> auto (*)() -> int (*)() { return nullptr; } int main() { cout << is_ same< decltype( pf), decltype( pf1)>:: value << endl; // 1 }
使用 追蹤 返回 類型, 能夠 實現 參數 和 返回 類型 不 同時 的 轉發。
double foo( int a) { return (double) a + 0. 1; } int foo( double b) { return (int) b; } template < class T> auto Forward( T t) -> decltype( foo( t)) { return foo( t); } int main() { cout << Forward( 2) << endl; // 2. 1 cout << Forward( 0. 5) << endl; // 0 }
如下內容是等價的。
auto (*fp)() -> int; int (*fp)(); auto (&fr)()->int; int (&fr)();
1五、
1六、
1七、