首頁  >  問答  >  主體

c++11 - c++move构造函数问题

PHP中文网PHP中文网2714 天前737

全部回覆(2)我來回復

  • PHP中文网

    PHP中文网2017-04-17 15:40:12

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

    首先本質原因是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。

    回覆
    0
  • 迷茫

    迷茫2017-04-17 15:40:12

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

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

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

    回覆
    0
  • 取消回覆