c++11提供了關鍵字noexcept
,用來指明某個函數沒法——或不打算——拋出異常:html
void foo() noexcept; // a function specified as will never throw void foo2() noexcept(true); // same as foo void bar(); // a function might throw exception void bar2() noexcept(false); // same as bar
因此咱們須要瞭解如下兩點:c++
noexcept
有什麼優勢,例如性能、可讀性等等。noexcept
。咱們先從std::vector入手來看一下第一點。git
咱們知道,vector有本身的capacity,當咱們調用push_back
可是vector容量滿時,vector會申請一片更大的空間給新容器,將容器內原有的元素copy到新容器內:github
可是若是在擴容元素時出現異常怎麼辦?函數
這種擴容方式比較完美,有異常時也會保持上游調用push_back
時原有的狀態。post
可是爲何說比較完美,由於這裏擴容仍是copy的,當vector內是一個類且持有資源較多時,這會很耗時。因此c++11推出了一個新特性:move
,它會將資源從舊元素中「偷」給新元素(對move不熟悉的同窗能夠本身查下資料,這裏不展開說了)。應用到vector擴容的場景中:當vector中的元素的移動拷貝構造函數是noexcept
時,vector就不會使用copy方式,而是使用move方式將舊容器的元素放到新容器中:性能
利用move
的交換類資源全部權的特性,使用vector擴容效率大大提升,可是當發生異常時怎麼辦:
原有容器的狀態已經被破壞,有部分元素的資源已經被偷走。若要恢復會極大增長代碼的複雜性和不可預測性。因此只有當vector中元素的move constructor
是noexcept
時,vector擴容纔會採起move方式來提升性能。this
剛纔總結了利用noexcept
如何提升vector擴容。實際上,noexcept
還大量應用在swap
函數和move assignment
中,原理都是同樣的。調試
上面提到了noexcept
可使用的場景:c++11
不少人的第一念頭多是:個人函數如今看起來明顯不會拋異常,又說聲明noexcept
編譯器能夠生成更高效的代碼,那能加就加唄。可是事實是這樣嗎?
這個問題想要討論清楚,咱們首先須要知道如下幾點:
noexcept
一致性檢查,例以下述代碼是合法的:void g(){ ... //some code } void f() noexcept { … //some code g(); }
noexcept
的函數拋出異常時,程序會被終止並調用std::terminate();因此在咱們的代碼內部調用複雜,鏈路較長,且隨時有可能加入新feature時,過早給函數加上noexcept
可能不是一個好的選擇,由於noexcept
一旦加上,後續再去掉也會變得困難 : 調用方有可能看到你的函數聲明爲noexcept,調用方也會聲明爲noexcept
。可是當你把函數的noexcept
去掉卻沒有修改調用方的代碼時,當異常拋出到調用方會致使程序終止。
目前主流的觀點是:
throw()
noexcept
。# if __cplusplus >= 201103L # define _GLIBCXX_NOEXCEPT noexcept # else # define _GLIBCXX_NOEXCEPT reference operator*() const _GLIBCXX_NOEXCEPT { return *_M_current; } pointer operator->() const _GLIBCXX_NOEXCEPT { return _M_current; } __normal_iterator& operator++() _GLIBCXX_NOEXCEPT { ++_M_current; return *this; } __normal_iterator operator++(int) _GLIBCXX_NOEXCEPT { return __normal_iterator(_M_current++); }
noexcept
就能夠。最後咱們看一下vector如何實現利用noexcept move constructor
擴容以及move constructor
是否聲明noexcept
對擴容的性能影響。
noexcept move constructor
擴容這裏就不貼大段的代碼了,每一個平臺的實現可能都不同,咱們只關注vector是怎麼判斷調用copy constructor
仍是move constructor
的。
其中利用到的核心技術有:
核心代碼:
template <typename _Iterator, typename _ReturnType = typename conditional< __move_if_noexcept_cond<typename iterator_traits<_Iterator>::value_type>::value, _Iterator, move_iterator<_Iterator>>::type> inline _GLIBCXX17_CONSTEXPR _ReturnType __make_move_if_noexcept_iterator(_Iterator __i) { return _ReturnType(__i); } template <typename _Tp> struct __move_if_noexcept_cond : public __and_<__not_<is_nothrow_move_constructible<_Tp>>, is_copy_constructible<_Tp>>::type {};
這裏用type trait
和iterator trait
聯合判斷:假如元素有noexcept move constructor
,那麼is_nothrow_move_constructible=1
=> __move_if_noexcept_cond=0
=> __make_move_if_noexcept_iterator
返回一個move iterator
。這裏move iterator
迭代器適配器也是一個c++11新特性,用來將任何對底層元素的處理轉換爲一個move操做,例如:
std::list<std::string> s; std::vector<string> v(make_move_iterator(s.begin()),make_move_iterator(s.end())); //make_move_iterator返回一個std::move_iterator
而後上游利用生成的move iterator
進行循環元素move:
{ for (; __first != __last; ++__first, (void)++__cur) std::_Construct(std::__addressof(*__cur), *__first); return __cur; } template <typename _T1, typename... _Args> inline void _Construct(_T1 *__p, _Args &&... __args) { ::new (static_cast<void *>(__p)) _T1(std::forward<_Args>(__args)...); //實際copy(或者move)元素 }
其中_Construct
就是實際copy(或者move)元素的函數。這裏很關鍵的一點是:對move iterator進行解引用操做,返回的是一個右值引用。,這也就保證了,當__first
類型是move iterator
時,用_T1(std::forward<_Args>(__args)...
進行「完美轉發」才調用_T1
類型的move constructor
,生成的新對象被放到新vector的__p
地址中。
總結一下過程就是:
type trait
和iterator trait
生成指向舊容器的normal iterator
或者move iterator
move iterator
,那麼解引用會返回右值引用,會調用元素的move constructor
,不然調用copy constructor
。你們能夠用下面這段簡單的代碼在本身的平臺打斷點調試一下:
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; } }; int main() { std::vector<A> v; for (int i = 0; i < 10; i++) { A a; v.push_back(a); } return 0; }
noexcept move constructor
對性能的影響這篇文章C++ NOEXCEPT AND MOVE CONSTRUCTORS EFFECT ON PERFORMANCE IN STL CONTAINERS介紹了noexcept move constructor對耗時以及內存的影響,這裏不重複贅述了,感興趣的能夠本身試一下。
參考資料:
(完)
朋友們能夠關注下個人公衆號,得到最及時的更新: