1. 程式人生 > >C++11 帶來的新特性 (3)—— 關鍵字noexcept

C++11 帶來的新特性 (3)—— 關鍵字noexcept

1 關鍵字noexcept

從C++11開始,我們能看到很多程式碼當中都有關鍵字noexcept。比如下面就是std::initializer_list 的預設建構函式,其中使用了noexcept。

      constexpr initializer_list() noexcept
      : _M_array(0), _M_len(0) { }

該關鍵字告訴編譯器,函式中不會發生異常,這有利於編譯器對程式做更多的優化。
如果在執行時,noexecpt函式向外丟擲了異常(如果函式內部捕捉了異常並完成處理,這種情況不算丟擲異常),程式會直接終止,呼叫std::terminate()函式,該函式內部會呼叫std::abort()終止程式。

2 C++的異常處理

C++中的異常處理是在執行時而不是編譯時檢測的。為了實現執行時檢測,編譯器建立額外的程式碼,然而這會妨礙程式優化。
在實踐中,一般兩種異常丟擲方式是常用的:

  • 一個操作或者函式可能會丟擲一個異常;
  • 一個操作或者函式不可能丟擲任何異常。

後面這一種方式中在以往的C++版本中常用throw()表示,在C++ 11中已經被noexcept代替。

    void swap(Type& x, Type& y) throw()   //C++11之前
    {
        x.swap(y);
    }
    void swap(Type& x, Type& y) noexcept  //C++11
    {
        x.swap(y);
    }

3 有條件的noexcecpt

在第2節中單獨使用noexcept,表示其所限定的swap函式絕對不發生異常。然而,使用方式可以更加靈活,表明在一定條件下不發生異常。

    void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y)))    //C++11
    {
        x.swap(y);
    }

它表示,如果操作x.swap(y)不發生異常,那麼函式swap(Type& x, Type& y)一定不發生異常。

一個更好的示例是std::pair中的移動分配函式(move assignment),它表明,如果型別T1和T2的移動分配(move assign)過程中不發生異常,那麼該移動建構函式就不會發生異常。

    pair& operator=(pair&& __p)
    noexcept(__and_<is_nothrow_move_assignable<_T1>,
                    is_nothrow_move_assignable<_T2>>::value)
    {
        first = std::forward<first_type>(__p.first);
        second = std::forward<second_type>(__p.second);
        return *this;
    }

4 什麼時候該使用noexcept?

使用noexcept表明函式或操作不會發生異常,會給編譯器更大的優化空間。然而,並不是加上noexcept就能提高效率,步子邁大了也容易扯著蛋。
以下情形鼓勵使用noexcept:

  • 移動建構函式(move constructor)
  • 移動分配函式(move assignment)
  • 解構函式(destructor)。這裡提一句,在新版本的編譯器中,解構函式是預設加上關鍵字noexcept的。下面程式碼可以檢測編譯器是否給解構函式加上關鍵字noexcept。
    struct X
    {
        ~X() { };
    };
    
    int main()
    {
        X x;
    
        // This will not fire even in GCC 4.7.2 if the destructor is
        // explicitly marked as noexcept(true)
        static_assert(noexcept(x.~X()), "Ouch!");
    }
  • 葉子函式(Leaf Function)。葉子函式是指在函式內部不分配棧空間,也不呼叫其它函式,也不儲存非易失性暫存器,也不處理異常。

最後強調一句,在不是以上情況或者沒把握的情況下,不要輕易使用noexception。