深刻理解C++11【2】

深刻理解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
View Code

   

  初始化 列表 還能夠 用於 函數 返回 的 狀況。 返回 一個 初始化 列表, 一般 會 致使 構造 一個 臨時 變量,\

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三、

相關文章
相關標籤/搜索