Home  >  Q&A  >  body text

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

PHP中文网PHP中文网2765 days ago769

reply all(2)I'll reply

  • PHP中文网

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

    The above explains why the copy constructor is called, and I will explain to you why it is messed up.

    First of all, the essential reason is vector expansion. The initial capacity is 0, the first operation expands to 1, and the second operation doubles to 2.

    You are using clang++ under mac. The stl implementation it calls should be libcxx. We can see it through the source code of vector push_back implementation in libcxx.

    The source code can be viewed here https://github.com/llvm-mirro...

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

    The implementation of __push_back_slow_path is like this

        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);

    It can be seen that the capacity is expanded first, __recommend is to do the expansion work, and then the new content is constructed and placed at the first place in the second half of __a. Note that there is forward, so it can be moved or copied, and finally executed_ _swap_out_circular_buffer function.
    The implementation of __swap_out_circular_buffer is as follows:

    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();
    }

    You don’t need to look at the processing of iterators and sizes. The key point is __alloc_traits::__construct_backward, which is responsible for copying or moving the data of the previous vector to the new vector memory area.
    The implementation of __construct_backward is like this

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

    It can be seen from move_if_noexcept that it must be noexcept to perform the move operation, and this operation starts from end, and the iterator decreases until begin, so it is in reverse order.


    So when you execute the first push_back, you check that the capacity is insufficient, execute the __push_back_slow_path function, expand the vector to 1, and execute a move constructor. Because the original vector is empty, no further processing is required.

    This is your first printing Move constructor is called. source: hello

    When you execute push_back for the second time, you check that the capacity is insufficient, execute the __push_back_slow_path function, expand the vector to 2, and execute a move constructor.

    This is your second printing Move constructor is called. source: world

    Then it executes __swap_out_circular_buffer and calls __alloc_traits::__construct_backward. Since your move constructor is not noexcept, it calls your copy constructor once.

    This is your third printing Copy constructor is called. source: hello


    If you have more elements, you will find that the execution order of subsequent copy constructors is reversed from the order of the original vector. The reason is also mentioned above. The operation starts from end, and the iterator continues to decrease to begin.

    reply
    0
  • 迷茫

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

    Because your move constructor is not noexcept. Declare the move constructor as noexcept(true), and the vector will not call the copy constructor.

    vector’s push_back may require extended storage area. This process involves copying the original data from the original storage area to the newly applied storage area. At the same time, push_back needs to ensure that if an operation throws an exception during the process of adding elements, the container maintains the state before push_back. Therefore, it cannot call the move constructor which may throw an exception. Because semantically speaking, interrupting the move construction operation (throwing an exception) will cause data corruption.

    It will call the copy constructor that may throw an exception because semantically speaking, it will not cause data corruption. Of course you can also do things in the copy constructor.

    reply
    0
  • Cancelreply