深入使用noexcept
簡(jiǎn)介
noexcept
是C++11引入的掷贾,表明函數(shù)是否會(huì)拋出異常。正確使用它可以優(yōu)化性能。
noexcept
使用語法有兩種:
- noexcpet
- noexcept(expression)
第二種使用方式允許用表達(dá)式?jīng)Q定是否noexcept
起效果,當(dāng)expression
的值為true的時(shí)候起效果,否則不起效绷雏。expression
是編譯時(shí)求值头滔,一切都是在編譯時(shí)決定。
好處
如果一個(gè)函數(shù)標(biāo)注成noexcept
涎显,
- 可以選擇移動(dòng)構(gòu)造函數(shù) / 移動(dòng)賦值運(yùn)算符拙毫;
- 編譯器就不用生成異常處理代碼了,因此可以優(yōu)化編譯棺禾。
壞處
如果noexcept的函數(shù)執(zhí)行時(shí)出了異常缀蹄,包括所調(diào)用的函數(shù)拋出的異常,程序會(huì)馬上terminate膘婶,即使套上try...catch
也仍舊會(huì)terminate缺前。并且編譯器不會(huì)幫你檢查這樣的風(fēng)險(xiǎn)。
適用場(chǎng)景
在需要決定是調(diào)用移動(dòng)構(gòu)造函數(shù)(或者移動(dòng)賦值運(yùn)算符)還是復(fù)制構(gòu)造函數(shù)(或者復(fù)制賦值運(yùn)算符)時(shí)悬襟,noexcept
會(huì)影響決定衅码。因?yàn)橐苿?dòng)語法會(huì)“破壞”原來的源對(duì)象的內(nèi)容,造成無法在出現(xiàn)異常情況下恢復(fù)狀態(tài)脊岳。因此只有移動(dòng)構(gòu)造函數(shù)(或者移動(dòng)賦值運(yùn)算符)標(biāo)明為noexcept
時(shí)逝段,才能在這種情況下使用移動(dòng)構(gòu)造函數(shù)(或者移動(dòng)賦值運(yùn)算符)替代復(fù)制構(gòu)造函數(shù)(或者復(fù)制賦值運(yùn)算符)。
一個(gè)例子就是STL庫中的vector
的擴(kuò)容割捅,擴(kuò)容涉及到是復(fù)制對(duì)象還是移動(dòng)對(duì)象的問題奶躯,就是上述的問題。當(dāng)對(duì)象的移動(dòng)構(gòu)造函數(shù)可能會(huì)拋出異常的時(shí)候亿驾,vector
是”不敢“在這個(gè)場(chǎng)景下調(diào)用的嘹黔,因?yàn)槌隽水惓o法原恢復(fù)狀態(tài)。
詳細(xì)邏輯如下:
- 當(dāng)使用復(fù)制構(gòu)造時(shí)莫瞬,拋出異常時(shí)只要把已復(fù)制的對(duì)象銷毀儡蔓,新分配的內(nèi)存釋放,一切還能恢復(fù)到跟以前一樣疼邀;
- 當(dāng)使用移動(dòng)構(gòu)造時(shí)喂江,容器中的原來的元素的狀態(tài)已經(jīng)被移動(dòng)構(gòu)造破壞了,無法恢復(fù)到跟以前一樣旁振。
以下是測(cè)驗(yàn)代碼:
class A {
public:
A() { std::cout << "constructor" << std::endl; }
A(const A& a) { std::cout << "copy constructor" << std::endl; }
A(const A&& a) noexcept { std::cout << "move constructor" << std::endl; } // 有noconcept時(shí)获询,擴(kuò)容時(shí)用移動(dòng)構(gòu)造
// A(const A&& a) { std::cout << "move constructor" << std::endl; } // 去掉noconcept時(shí),擴(kuò)容時(shí)用拷貝構(gòu)造
};
int main() {
std::vector<A> v;
v.reserve(1);
for (int i = 0; i < 10; i++) {
A a; // 構(gòu)造一個(gè)A類的實(shí)例规求。
v.push_back(a); // 添加進(jìn)容器時(shí)會(huì)調(diào)用一次復(fù)制構(gòu)造筐付,如果容量不夠則會(huì)擴(kuò)容卵惦,這時(shí)候會(huì)選擇復(fù)制構(gòu)造還是移動(dòng)構(gòu)造阻肿。
}
return 0;
}
不適用場(chǎng)景
其他情況均不太適合使用。因?yàn)椋?/p>
- 編譯器不會(huì)幫你做檢查沮尿,假如一個(gè)標(biāo)注noexcept的函數(shù)調(diào)用未標(biāo)注noexcept的函數(shù)丛塌,是可以順利編譯的较解。但是未標(biāo)注nonexcept的函數(shù)是不保證不拋異常的。
- 一個(gè)函數(shù)加上
noexcept
之后就可能很難移除赴邻,因?yàn)槠渌a可能會(huì)直接或間接引用到它印衔,且假設(shè)不會(huì)有異常; - 如果一個(gè)標(biāo)注了
noexcept
的函數(shù)自己或者調(diào)用的函數(shù)(直接或間接)拋出異常姥敛,直接終止奸焙,非常簡(jiǎn)單粗暴。
因此出了幾個(gè)有限的適用場(chǎng)景外彤敛,其他情況下不要用noexcept
与帆。
實(shí)驗(yàn)結(jié)果
以下圖表是來自于 C++ noexcept and move constructors effect on performance in STL Containers — TRYING TO FIND THE OBVIOUS (hlsl.co.uk) 這篇博客的實(shí)驗(yàn)結(jié)果。
根據(jù)實(shí)驗(yàn)結(jié)果墨榄,性能提升了兩倍玄糟!
總結(jié)
雖然noexcept
會(huì)在某些情況下提升性能,但是由于它的危險(xiǎn)性袄秩,包括發(fā)生異常直接終止程序且編譯器不會(huì)幫你檢查阵翎,除了以下情況下都不建議使用。
- 移動(dòng)構(gòu)造函數(shù)
- 移動(dòng)賦值運(yùn)算符
- 析構(gòu)函數(shù)
- 簡(jiǎn)單函數(shù)
1之剧、2 已經(jīng)在前面說過了郭卫,不再贅述。對(duì)于3背稼、4箱沦,析構(gòu)函數(shù)本身不應(yīng)該拋異常,簡(jiǎn)單函數(shù)一般不會(huì)發(fā)生異常雇庙,因此可以放心標(biāo)注noexcept
谓形。