總覽
新機制
對
noexcept
修飾的函數進行優化.
noexcept
可以傳導和查詢.異常
用異常代替
if else
.將
if else
統一到一個地方處理.異常
必須處理的錯誤,直接拋出異常.
不需要處理的錯誤.
noexcept
函數
任意時刻調用都不會出錯.
noexcept
可以是非noexcept
組成.主流函數
大部分都是可能拋出異常的.
不一定非要追求
noexcept
,不要用return code
代替異常.錯誤檢測
調用者保障.
直接拋出異常.
C++
的異常
C++98
異常
可以通過
throw(...)
枚舉可能拋出的異常. 如果拋出了預期外的則終止程序.但是統一性的問題. 使用者很可能會對使用的函數將要
throw
的異常比較關注.
C++98
一致性
使用程序的開發者對函數進行了修改,拋出的異常變化了.
那麼就需要改動
throw(...)
裏面的枚舉,那麼就會導致一些列相關的代碼也需要修改.帶來的問題,編譯器也不會提醒,就會帶來一系列的問題.
這種設計是不好的,但是有些場景又是必須的.
C++11
只有兩種狀態: 肯定不會 和 可能會 .
noexcept
或noexcept(true)
修飾的肯定不會. 其他則可能會.
C++11
和C++98
的差異
C++11
的noexcept
修飾,即肯定不會拋異常的函數會進行優化.
C++98
則會嘗試優化,但是還是會保留一些額外的信息.
C++98
不拋出異常:throw()
,C++11
則是noexcept,noexcept(true)
.
C++98
出現期望之外的異常執行析構.C++11
遇到期望之外的會立即崩潰,不會清理資源.被優化掉了.接口設計
在設計接口的時候就應該明確,是否可能會拋出異常.
而且接口需要穩定,遵循開閉原則.
函數是否異常的影響
影響編譯.
影響使用者的代碼實現和架構設計。
重要性
和函數的
const
修飾一樣.但是
noexcept
不參與重載修飾.
C++98
和C++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
.
wide
和narrow
區分
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
的效率高,但是不要可以強求.
noexcept
在move,swap
析構這些場景非常適合.主流的是拋異常函數而非
noexcept
.