C++11 推薦用 const 類型的迭代器

  • 總覽

    • 新機制

      • noexcept修飾的函數進行優化.

      • noexcept可以傳導和查詢.

    • 異常

      • 用異常代替if else.

      • if else統一到一個地方處理.

    • 異常

      • 必須處理的錯誤,直接拋出異常.

      • 不需要處理的錯誤.

    • noexcept函數

      • 任意時刻調用都不會出錯.

      • noexcept可以是非noexcept組成.

    • 主流函數

      • 大部分都是可能拋出異常的.

      • 不一定非要追求noexcept,不要用return code代替異常.

    • 錯誤檢測

      • 調用者保障.

      • 直接拋出異常.

  • C++的異常

    • C++98異常

      • 可以通過throw(...)枚舉可能拋出的異常. 如果拋出了預期外的則終止程序.

      • 但是統一性的問題. 使用者很可能會對使用的函數將要throw的異常比較關注.

    • C++98一致性

      • 使用程序的開發者對函數進行了修改,拋出的異常變化了.

      • 那麼就需要改動throw(...)裏面的枚舉,那麼就會導致一些列相關的代碼也需要修改.

      • 帶來的問題,編譯器也不會提醒,就會帶來一系列的問題.

      • 這種設計是不好的,但是有些場景又是必須的.

    • C++11

      • 只有兩種狀態: 肯定不會可能會 .

      • noexceptnoexcept(true) 修飾的肯定不會. 其他則可能會.

    • C++11C++98的差異

      • C++11noexcept修飾,即肯定不會拋異常的函數會進行優化.

      • C++98則會嘗試優化,但是還是會保留一些額外的信息.

      • C++98不拋出異常:throw(),C++11則是noexcept,noexcept(true).

      • C++98出現期望之外的異常執行析構. C++11 遇到期望之外的會立即崩潰,不會清理資源.被優化掉了.

    • 接口設計

      • 在設計接口的時候就應該明確,是否可能會拋出異常.

      • 而且接口需要穩定,遵循開閉原則.

    • 函數是否異常的影響

      • 影響編譯.

      • 影響使用者的代碼實現和架構設計。

    • 重要性

      • 和函數的const修飾一樣.

      • 但是noexcept不參與重載修飾.

    • C++98C++11差異案例

      • C++98會清理資源,返回到調用者,再崩潰.

      [root@localhost test]# g++ test.cpp -std=c++98
      [root@localhost test]# cat test.cpp
      #include <iostream>
      
      class T {
      public:
         T() {std::cout << __FUNCTION__ << std::endl;}
         ~T() {std::cout << __FUNCTION__ << std::endl;}
      };
      
      void show() throw()
      {
         T t;
         throw 1;
      }
      
      int main() {
         show();
      }
      [root@localhost test]# ./a.out
      T
      ~T
      terminate called after throwing an instance of 'int'
      Aborted
      
      
      • C++11不會,直接崩潰.棧回指只會發生在程序結束.編譯器直接把釋放的代碼給優化掉了.不需要.因爲已經保證肯定不會拋出異常.

      [root@localhost test]# g++ test.cpp -std=c++11
      [root@localhost test]# cat test.cpp
      #include <iostream>
      
      class T {
      public:
         T() {std::cout << __FUNCTION__ << std::endl;}
         ~T() {std::cout << __FUNCTION__ << std::endl;}
      };
      
      void show() noexcept
      {
         T t;
         throw 1;
      }
      
      int main() {
         show();
      }
      [root@localhost test]# ./a.out
      T
      terminate called after throwing an instance of 'int'
      Aborted
      
      
    • 編譯器優化

      • void func() noexcept:全力優化; void func() throw():簡單優化; void func():簡單優化;

    • 小結

      • noexcept函數會被編譯器優化這一點,開發者就應該對肯定不會拋出異常的函數加上這個修飾.

  • noexcept場景分析

    • std::vector添加元素.

      • 添加導致,但是內存不足需要擴容.

    • 正確性問題

      • 如果拷貝的時候出現異常.數據如何處理.copy中發生異常,原來的數據還是完整的.

      • 拷貝失敗,捕獲還是什麼?

      • C++11採用move的方式優化,但是問題是,move到一半,發生異常,原來的數據被修改,也無法回滾.

    • C++11做法

      • 拷貝根據類型的拷貝構造是否會拋出異常,選擇使用元素的拷貝構造還是移動構造.

    • 案例

      #include <iostream>
      
      class T {
      public:
         T() {std::cout << this << std::endl;}
         T(const T& t) noexcept {std::cout << "copy"  << this << std::endl;}
         T(T&& t) noexcept {std::cout << "move"  << this << std::endl;}
         ~T() {std::cout << this << std::endl;}
      };
      
      void show(T t) noexcept(noexcept(T(std::move(t)))){
         if(noexcept(T(std::move(t)))) {
             std::cout << "noexcept t " << &t << std::endl;
             T d(std::move(t));
             std::cout << "noexcept d " << &d << std::endl;
         } else {
             std::cout << "except t " << &t << std::endl;
             T d(t);
             std::cout << "except d " << &d << std::endl;
         }
      }
      
      int main() {
         T t;
         show(t);
      }
      
      • noexcept版本的move.

      0x61fe1e
      copy 0x61fe1f
      noexcept t 0x61fe1f
      move 0x61fddf
      noexcept d 0x61fddf
      0x61fddf
      0x61fe1f
      0x61fe1e
      [Finished in 485ms]
      
      • except版本

      #include <iostream>
      
      class T {
      public:
         T() {std::cout << this << std::endl;}
         T(const T& t) noexcept {std::cout << "copy" << this << std::endl;}
         T(T&& t)  {std::cout << this << "move" << std::endl;}
         ~T() {std::cout << this << std::endl;}
      };
      
      void show(T t) noexcept(noexcept(T(std::move(t)))){
         if(noexcept(T(std::move(t)))) {
             std::cout << "noexcept t " << &t << std::endl;
             T d(std::move(t));
             std::cout << "noexcept d " << &d << std::endl;
         } else {
             std::cout << "except t " << &t << std::endl;
             T d(t);
             std::cout << "except d " << &d << std::endl;
         }
      }
      
      int main() {
         T t;
         show(t);
      }
      
      • 輸出

      0x61fd3e
      copy 0x61fd3f
      except t 0x61fd3f
      copy 0x61fbae
      except d 0x61fbae
      0x61fbae
      0x61fd3f
      0x61fd3e
      [Finished in 472ms]
      
    • 建議

      • 拷貝,移動,swap這些函數實現成noexcept.

      • 拷貝和移動,析構默認是noexcept,但是默認生成的行爲不符合預期.

    • 擴展

      #include <iostream>
      void show(int a) noexcept(noexcept(a) && noexcept(a)){
      }
      
      int main() {
         int a;
         show(a);
      }
      
      • 可以多個表達式,與或非.

  • 優化和noexcept

    • 回顧

      • 前面的優化方案令人心動.是不是聲明瞭程序就可以跑得更快了。

      • 但是正確性和開發效率,代碼整潔等等都是需要考量的。

    • 設計

      • 先聲明爲noexcept,後期再改回來?

      • 隨意改動會影響到其他使用到這個函數的人,不建議隨意修改,自己使用另說.

    • 改成noexcept

      • 修改成noexcept,但是使用了可能拋異常的,就會導致異常無法傳遞,然後崩潰.

    • 主流

      • 都是 異常中立函數. 異常中立函數就是沒有聲明任何異常,也不是noexcept.

      • 這類可以傳遞使用的代碼拋出的異常. 然後不做處理就直接放行.

    • 默認noexcept

      • 編譯器提供的拷貝和移動.

    • 肯定不會拋異常的函數

      • 建議使用noexcept修飾.

    • 異常和返回值

      • 將異常捕獲改成返回值的方式,然後把函數改成noexcept,這種就有點本末倒置了.

  • noexcept和開發

    • noexcept

      • 雖然執行效率高,但是適用性不是特別廣.

    • 異常的好處

      • 使用exception替代返回值的方式可以提高開發效率.

      • 可以減少因爲返回值帶來的if else判斷分支.

      • 可以避免if else帶來的函數膨脹.

      • 可以簡化代碼結構.將if else放到另外的地方處理.

    • 返回值的弊端

      • 雖然noexcept可能會很快,但是代價就是導致代碼複雜.

      • 代碼結構混亂,分支變多,維護麻煩.

      • 帶來的一系列問題和開銷可能還不如用異常來處理.

    • 提供功能

      • 功能提供者不應該檢測數據的有效性.

      • 應該由調用者確保數據的合法性.

      • 外觀模式的主要核心就是保障合法.

      • 出現問題則就應該直接拋出異常,讓調用者自行處理.

  • 函數設計

    • wide contracts | narrow contracts

      • wide就是不檢測入參無限制,程序任意時刻可運行.

      • narrow就是入參有限制,錯誤參數程序會出錯.

    • wide常見就是

      #include <iostream>
      void show(int a){
      }
      
      int main() {
         int a;
         show(a);
      }
      
      • 無任何要求,什麼值都可以.

    • narrow

      #include <iostream>
      void show(int a){
         int c = 10 / a;
      }
      
      int main() {
         int a;
         show(a);
      }
      
      • a明顯不能等於0. 但是不會檢查,由調用者保證有效.

    • noexcept

      • 這類最可能將函數聲明爲noexcept.

    • narrow

      • 沒有責任檢測合法性, 會帶來很多分支和性能的開銷.

      • 應該由調用者檢查合法性,有的時候甚至不需要檢測.

      • 檢查不強制,函數實現者也可以在函數內檢測,不反對但是不推薦.

  • noexcept不一定非要noexcept實現

    • wide

      • 什麼場景都不會出現問題,直接noexcept.

    • widenarrow區分

      • wide一般用noexcept,narrow則沒有.

    • noexcept調用非noexcept

      #include<iostream>
      void setup(); // functions defined elsewhere
      void cleanup();
      void doWork() noexcept
      {
      setup(); // set up work to be done
      // … // do the actual work
      cleanup(); // perform cleanup actions
      }
      
      int main() {
         doWork();
      }
      
      void setup() {
         printf("%s\n",__FUNCTION__);
      }
      
      void cleanup() {
         printf("%s\n",__FUNCTION__);
      }
      
      
      • 調用非noexcept,可能是C函數,沒有noexcept.

      • 或者就是C++98的,沒有noexcept.

  • 總結

    • noexcept對使用者很重要.

    • noexcept的效率高,但是不要可以強求.

    • noexceptmove,swap析構這些場景非常適合.

    • 主流的是拋異常函數而非noexcept.

  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章