<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
但是筆者卻發現了一個奇怪的現象,std::vector
擴容時,對其中的元素竟然進行的是深複製。請看範例程式碼:
#include <iostream> #include <vector> struct Test { Test() {std::cout << "Test" << std::endl;} ~Test() {std::cout << "~Test" << std::endl;} Test(const Test &) {std::cout << "Test copy" << std::endl;} Test(Test &&) {std::cout << "Test move" << std::endl;} }; int main(int argc, const char *argv[]) { std::vector<Test> ve; ve.emplace_back(); ve.emplace_back(); ve.emplace_back(); return 0; }
列印結果如下:
Test
Test
Test copy
~Test
Test
Test copy
Test copy
~Test
~Test
~Test
~Test
~Test
由於我們沒有呼叫reverse
函數,所以預設只分配了一個元素的大小。第一次emplace_back
時,僅進行了一次普通構造。第二次emplace_back
時,就需要進行擴容,然後把第一個元素拷貝過去,再釋放原來的物件。所以這裡除了有一次新的構造以外,還有一次複製和釋放。後面的行為類似,不再贅述,
但關鍵問題就在於,Test
類明明實現了移動構造(淺複製),可這裡竟然呼叫了拷貝構造(深複製)。
如果vector
擴容無腦呼叫拷貝構造,那麼這個物件如果含有很多外連的成員(比如說指向buffer的指標、指向其他物件的指標等),呼叫拷貝構造就意味著要把這些連結的物件全部都重新構造一遍。這對於vector
自身擴容來說,顯然是沒有必要的,會極度浪費記憶體空間。
基於上述理由,我認為STL的開發者不可能連這個問題都考慮不到,但想不通為什麼我明明實現了移動構造,卻不能呼叫。
帶著這樣的疑問我去研讀了STL的原始碼(GNU版本),在vector
擴容時,會呼叫_M_realloc_insert
函數,該函數在vector.tcc檔案中實現。在這個函數裡面對已有元素進行拷貝的時候,看到了類似這樣的程式碼:
__new_finish = std::__uninitialized_move_if_noexcept_a (__old_start, __position.base(), __new_start, _M_get_Tp_allocator()); ++__new_finish;
有趣的就是這個__uninitialized_move_if_noexcept_a
,我們找到這個函數的實現:
template<typename _InputIterator, typename _ForwardIterator, typename _Allocator> inline _ForwardIterator __uninitialized_move_if_noexcept_a(_InputIterator __first, _InputIterator __last, _ForwardIterator __result, _Allocator& __alloc) { return std::__uninitialized_copy_a (_GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(__first), _GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(__last), __result, __alloc); }
再看一下_GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR
的實現
#if __cplusplus >= 201103L #define _GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(_Iter) std::__make_move_if_noexcept_iterator(_Iter) #else #define _GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(_Iter) (_Iter) #endif // C++11
也就是說,在C++11以前,這玩意就是物件本身(畢竟C++11以前還沒有移動構造),而在C++11以後被定義成了__make_move_if_noexcept_iterator
,繼續檢視其定義。
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); }
這裡用了一個conditional
,來判斷這個迭代器的型別,如果__move_if_noexcept_cond
為真,就取迭代器本身,否則就取移動迭代器。看起來問題就在這裡了,之前我們的例程中的Test一定就是符合了這個__move_if_noexcept_cond
,導致用了原始迭代器。
繼續深挖這個__move_if_noexcept_cond
,看到這樣的程式碼:
template<typename _Tp> struct __move_if_noexcept_cond : public __and_<__not_<is_nothrow_move_constructible<_Tp>>, is_copy_constructible<_Tp>>::type { };
也就是說,如果一個類,不存在不會丟擲異常的移動建構函式並且可拷貝,那麼就為真。
Test類顯然符合,所以vector<Test>
在複製時用了普通的迭代器進行了遍歷,自然就會呼叫拷貝建構函式進行復制了。
所以,我們需要讓Test
不符合__move_if_noexcept_cond
的條件,也就是這裡要將移動建構函式宣告為noexcept
表示它不會丟擲異常,這樣vector<Test>
在複製時就會使用移動迭代器(就是會包裝一層std::move
),從而觸發移動構造。
順道我們也看一眼移動迭代器的原理:
template<typename _Iterator> class move_iterator { _Iterator _M_current; // ... public: using iterator_type = _Iterator; explicit _GLIBCXX17_CONSTEXPR move_iterator(iterator_type __i) : _M_current(std::move(__i)) { } // ... }
確實呼叫了std::move
,證明我們的思路沒錯。
所以,修改Test
程式碼,實現noexcept
移動構造:
struct Test { long a, b, c, d; Test() {std::cout << "Test" << std::endl;} ~Test() {std::cout << "~Test" << std::endl;} Test(const Test &) {std::cout << "Test copy" << std::endl;} Test(Test &&) noexcept {std::cout << "Test move" << std::endl;} }; int main(int argc, const char *argv[]) { std::vector<Test> ve; ve.emplace_back(); ve.emplace_back(); ve.emplace_back(); return 0; }
列印結果如下:
Test
Test
Test move
~Test
Test
Test move
Test move
~Test
~Test
~Test
~Test
~Test
這次如我們所願,呼叫了移動構造。
STL中考慮到異常的情況,因此,像這種容器內部的複製行為,是要求不能夠發生異常的,因此,只有當移動建構函式宣告為noexcept
的時候才會呼叫,否則將統一呼叫拷貝建構函式。
然而,在移動建構函式中本來就不應該丟擲異常,因此,在大多數情況下,移動建構函式都應該用noexcept
來宣告。
到此這篇關於C++ STL標準庫std::vector擴容時進行深複製原因詳解的文章就介紹到這了,更多相關C++ std::vector內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45