中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++ vector擴容解析noexcept的應用場景有哪些

發布時間:2020-11-02 17:40:58 來源:億速云 閱讀:186 作者:Leah 欄目:開發技術

C++ vector擴容解析noexcept的應用場景有哪些?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

c++11提供了關鍵字noexcept,用來指明某個函數無法——或不打算——拋出異常:

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

所以我們需要了解以下兩點:

noexcept有什么優點,例如性能、可讀性等等。

需不需要在代碼中大量使用noexcept。

noexcept優點

我們先從std::vector入手來看一下第一點。

我們知道,vector有自己的capacity,當我們調用push_back但是vector容量滿時,vector會申請一片更大的空間給新容器,將容器內原有的元素copy到新容器內:

C++ vector擴容解析noexcept的應用場景有哪些

但是如果在擴容元素時出現異常怎么辦?

申請新空間時出現異常:舊vector還是保持原有狀態,拋出的異常交由用戶自己處理。

copy元素時出現異常:所有已經被copy的元素利用元素的析構函數釋放,已經分配的空間釋放掉,拋出的異常交由用戶自己處理。

這種擴容方式比較完美,有異常時也會保持上游調用push_back時原有的狀態。

但是為什么說比較完美,因為這里擴容還是copy的,當vector內是一個類且持有資源較多時,這會很耗時。所以c++11推出了一個新特性:move,它會將資源從舊元素中“偷”給新元素(對move不熟悉的同學可以自己查下資料,這里不展開說了)。應用到vector擴容的場景中:當vector中的元素的移動拷貝構造函數是noexcept時,vector就不會使用copy方式,而是使用move方式將舊容器的元素放到新容器中:

C++ vector擴容解析noexcept的應用場景有哪些

利用move的交換類資源所有權的特性,使用vector擴容效率大大提高,但是當發生異常時怎么辦:
原有容器的狀態已經被破壞,有部分元素的資源已經被偷走。若要恢復會極大增加代碼的復雜性和不可預測性。所以只有當vector中元素的move constructor是noexcept時,vector擴容才會采取move方式來提高性能。

剛才總結了利用noexcept如何提高vector擴容。實際上,noexcept還大量應用在swap函數和move assignment中,原理都是一樣的。

noexcept使用場景

上面提到了noexcept可以使用的場景:

  • move constructor
  • move assignment
  • swap
     

很多人的第一念頭可能是:我的函數現在看起來明顯不會拋異常,又說聲明noexcept編譯器可以生成更高效的代碼,那能加就加唄。但是事實是這樣嗎?

這個問題想要討論清楚,我們首先需要知道以下幾點:

函數自己不拋異常,但是不代表它們內部的調用不會拋出異常,并且編譯器不會提供調用者與被調用者的noexcept一致性檢查,例如下述代碼是合法的:

void g(){
  ...    //some code
}
void f() noexcept
{
  … 			//some code
  g();
}

當一個聲明為noexcept的函數拋出異常時,程序會被終止并調用std::terminate();

所以在我們的代碼內部調用復雜,鏈路較長,且隨時有可能加入新feature時,過早給函數加上noexcept可能不是一個好的選擇,因為noexcept一旦加上,后續再去掉也會變得困難 : 調用方有可能看到你的函數聲明為noexcept,調用方也會聲明為noexcept。但是當你把函數的noexcept去掉卻沒有修改調用方的代碼時,當異常拋出到調用方會導致程序終止。

目前主流的觀點是:

加noexcept

函數在c++98版本中已經被聲明為throw()

上文提到過的三種情況:move constructor、move assignmemt、swap。如果這些實現不拋出異常,一定要使用noexcept。
leaf function. 例如獲取類成員變量,類成員變量的簡單運算等。下面是stl的正向iterator中的幾個成員函數:

# 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

除了上面的要加的情況,其余的函數不要加noexcept就可以。

最后我們看一下vector如何實現利用noexcept move constructor擴容以及move constructor是否聲明noexcept對擴容的性能影響。

如何實現利用noexcept move constructor擴容

這里就不貼大段的代碼了,每個平臺的實現可能都不一樣,我們只關注vector是怎么判斷調用copy constructor還是move constructor的。

其中利用到的核心技術有:

  • type trait
  • iterator trait
  • move iterator
  • std::forward

核心代碼:

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對耗時以及內存的影響,這里不重復贅述了,感興趣的可以自己試一下。

C++ vector擴容解析noexcept的應用場景有哪些

看完上述內容,你們掌握C++ vector擴容解析noexcept的應用場景有哪些的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新绛县| 喀喇沁旗| 大连市| 东平县| 建平县| 尚志市| 靖宇县| 鞍山市| 阳泉市| 西宁市| 肥西县| 农安县| 沾益县| 乌什县| 大姚县| 寻乌县| 太湖县| 绥德县| 泸定县| 海口市| 盐源县| 睢宁县| 子长县| 昌平区| 丰台区| 昌都县| 和林格尔县| 德惠市| 长治县| 布尔津县| 会宁县| 大冶市| 商河县| 汾阳市| 格尔木市| 桃源县| 鄂托克前旗| 朔州市| 德昌县| 文安县| 宁南县|