首页  >  问答  >  正文

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

PHP中文网PHP中文网2714 天前738

全部回复(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
  • 取消回复