std :: vector emplace_back可以从向量本身的元素构造吗?

时间:2014-07-23 11:02:15

标签: c++ c++11 vector stl

当使用push_back std::vector时,我可以推送向量本身的元素,而不必担心因重新分配而使参数无效:

std::vector<std::string> v = { "a", "b" };
v.push_back(v[0]); // This is ok even if v.capacity() == 2 before this call.

但是,使用emplace_back时,std::vector会将参数转发给std::string的构造函数,以便复制构造在向量中发生。这使我怀疑向量的重新分配发生在新字符串被复制构造之前(否则它不会被分配到位),从而在使用之前使参数无效。

这是否意味着我无法使用emplace_back添加向量本身的元素,或者在重新分配时是否有某种保证,类似于push_back

在代码中:

std::vector<std::string> v = { "a", "b" };
v.emplace_back(v[0]); // Is this valid, even if v.capacity() == 2 before this call?

3 个答案:

答案 0 :(得分:19)

出于同样的原因,{p> emplace_back必须是安全的push_back is required to be safe;一旦修改方法调用返回,指针和引用的失效才有效。

实际上,这意味着执行重新分配的emplace_back需要按以下顺序进行(忽略错误处理):

  1. 分配新容量
  2. Emplace - 在新数据段末尾构建新元素
  3. 将现有元素移动构建到新数据段
  4. 销毁和取消分配旧数据段
  5. this reddit thread STL确认VC11无法支持v.emplace_back(v[0])作为错误,因此您一定要检查您的库是否支持此用法,而不是理所当然。

    请注意,标准明确禁止某些形式的自我插入 ;例如在 [sequence.reqmts] 第4段表100 a.insert(p,i,j)具有先决条件&#34; ij不是a的迭代器{1}} &#34;

答案 1 :(得分:2)

与其他一些人在这里写的相反,我本周的体验是不安全,至少在尝试使用具有已定义行为的可移植代码时。

以下是一些可能会暴露未定义行为的示例代码:

std::vector<uint32_t> v;
v.push_back(0);
// some more push backs into v followed but are not shown here...

v.emplace_back(v.back()); // problem is here!

上面的代码在Linux上使用g ++ STL运行没有问题。

在Windows上运行相同的代码(使用Visual Studio 2013 Update5编译)时,向量有时会包含一些乱码元素(看似随机的值)。

原因是v.back()返回的引用由于容器在最后添加元素之前达到v.emplace_back()内的容量限制而失效。

我查看了VC ++的emplace_back()的STL实现,它似乎分配了新的存储,将现有的矢量元素复制到新的存储位置,释放旧的存储,然后构建新存储结束时的元素。此时,引用元素的底层内存可能已经被释放或以其他方式无效。这产生了未定义的行为,导致在重新分配阈值处插入的向量元素出现乱码。

这似乎是一个(仍未修复)bug in Visual Studio。 使用我试过的其他STL实现,问题没有发生。

最后,您应该避免将向量元素的引用传递给同一向量的emplace_back(),至少如果您的代码是使用Visual Studio编译的并且应该可以工作的话。

答案 2 :(得分:1)

我检查了我的矢量实现,它的工作原理如下:

  1. 分配新内存
  2. Emplace对象
  3. Dealloc旧记忆
  4. 所以一切都很好。类似的实现用于push_back,所以这个很好。

    仅供参考,这是实施的相关部分。我添加了评论:

    template<typename _Tp, typename _Alloc>
        template<typename... _Args>
          void
          vector<_Tp, _Alloc>::
          _M_emplace_back_aux(_Args&&... __args)
          {
        const size_type __len =
          _M_check_len(size_type(1), "vector::_M_emplace_back_aux");
    // HERE WE DO THE ALLOCATION
        pointer __new_start(this->_M_allocate(__len));
        pointer __new_finish(__new_start);
        __try
          {
    // HERE WE EMPLACE THE ELEMENT
            _Alloc_traits::construct(this->_M_impl, __new_start + size(),
                         std::forward<_Args>(__args)...);
            __new_finish = 0;
    
            __new_finish
              = std::__uninitialized_move_if_noexcept_a
              (this->_M_impl._M_start, this->_M_impl._M_finish,
               __new_start, _M_get_Tp_allocator());
    
            ++__new_finish;
          }
        __catch(...)
          {
            if (!__new_finish)
              _Alloc_traits::destroy(this->_M_impl, __new_start + size());
            else
              std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
            _M_deallocate(__new_start, __len);
            __throw_exception_again;
          }
        std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
                  _M_get_Tp_allocator());
    // HERE WE DESTROY THE OLD MEMORY
        _M_deallocate(this->_M_impl._M_start,
                  this->_M_impl._M_end_of_storage
                  - this->_M_impl._M_start);
        this->_M_impl._M_start = __new_start;
        this->_M_impl._M_finish = __new_finish;
        this->_M_impl._M_end_of_storage = __new_start + __len;
          }