c++11 - c++move构造函数问题
PHP中文网
PHP中文网 2017-04-17 15:38:12
0
2
819
PHP中文网
PHP中文网

认证高级PHP讲师

全部回覆(2)
洪涛

樓上解釋了為什麼會呼叫拷貝構造函數,我再給你解釋為什麼會亂入。

首先本質原因是vector擴容,一開始容量是0,第一次操作擴容到1,第二次是翻倍為2。

你是用mac下的clang++的,它呼叫的stl實作應該是libcxx,我們可以透過libcxx裡vector的push_back實作原始碼看出來。

原始碼可以在這裡查看 https://github.com/llvm-mirro...

 if (this->__end_ != this->__end_cap())
    {
    }
    else
        __push_back_slow_path(__x);

__push_back_slow_path的實作是這樣的

    allocator_type& __a = this->__alloc();
    __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
    __alloc_traits::construct(__a, _VSTD::__to_raw_pointer(__v.__end_), _VSTD::forward<_Up>(__x));
    __v.__end_++;
    __swap_out_circular_buffer(__v);

可以看出是先擴容,__recommend就是做擴容的工作,隨後把新的內容構造出來放在__a的後半段的首位,注意有forward,所以可移動也可拷貝,最後再執行_ _swap_out_circular_buffer函數。
__swap_out_circular_buffer的實作是這樣的:

template <class _Tp, class _Allocator>
void
vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v)
{
    __annotate_delete();
    __alloc_traits::__construct_backward(this->__alloc(), this->__begin_, this->__end_, __v.__begin_);
    _VSTD::swap(this->__begin_, __v.__begin_);
    _VSTD::swap(this->__end_, __v.__end_);
    _VSTD::swap(this->__end_cap(), __v.__end_cap());
    __v.__first_ = __v.__begin_;
    __annotate_new(size());
    __invalidate_all_iterators();
}

對於迭代器的處理和對於size的處理可以不用看,重點是__alloc_traits::__construct_backward,它負責把之前vector的資料拷貝或移到新vector記憶體區。
__construct_backward的實作是這樣的

while (__end1 != __begin1)
{
   construct(__a, _VSTD::__to_raw_pointer(__end2-1), _VSTD::move_if_noexcept(*--__end1));
   --__end2;
}

可以從move_if_noexcept看出它要執行移動操作必須保證是noexcept的,而且這個操作是從end開始的,迭代器一直遞減到begin,所以是逆序的。


所以在你執行第一次push_back時,檢查了容量不夠,執行__push_back_slow_path函數,vector擴容到1,並且執行了一次移動構造函數,因為原來的vector是空的,所以不需要進一步處理。

此時就是你的第一次印刷 Move constructor is called. source: hello

在你執行第二次push_back時,檢查了容量不夠,執行__push_back_slow_path函數,vector擴容到2,並且執行了一次移動構造函數。

此時就是你的第二次列印 Move constructor is called. source: world

隨後它執行__swap_out_circular_buffer,並呼叫__alloc_traits::__construct_backward,由於你的移動建構子不是noexcept的,所以它呼叫了一次你的拷貝建構子。

此時就是你的第三次印刷 Copy constructor is called. source: hello


如果你有更多的元素,你會發現後續的拷貝構造函數執行順序是和原vector的順序反著的,原因上面也說了,操作是從end開始的,迭代器一直遞減到begin。

迷茫

因為你的移動建構子不是noexcept的。將移動建構函式宣告為noexcept(true),vector就不會呼叫拷貝建構函式了。

vector的push_back可能會需要擴充儲存區。此流程要將原有資料從原始儲存區拷貝到新申請的儲存區。同時push_back需要確保在加入元素的過程中,若有操作拋出異常,容器保持push_back前的狀態。因此,它不能呼叫可能會拋出異常的移動構造函數。因為從語意上來說,移動構造操作中斷(拋出異常)會導致資料損壞。

它會呼叫可能拋異常的拷貝建構函數,是因為從語意上來說這麼做不會導致資料損壞。當然你也可以在拷貝建構函式裡搞事情。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板