C++ std::vector是否可以从vector本身的元素中放置并复制构造?

C++ std::vector是否可以从vector本身的元素中放置并复制构造?,c++,c++11,vector,stl,C++,C++11,Vector,Stl,使用std::vector的push_back时,我可以推送向量本身的元素,而不用担心由于重新分配而使参数无效: std::vector<std::string> v = { "a", "b" }; v.push_back(v[0]); // This is ok even if v.capacity() == 2 before this call. 我检查了我的向量实现,它在这里工作如下: 分配新内存 侵位体 解除锁定旧内存 所以这里一切都很好。类似的实现用于push_-back

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

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

我检查了我的向量实现,它在这里工作如下:

  • 分配新内存
  • 侵位体
  • 解除锁定旧内存
  • 所以这里一切都很好。类似的实现用于
    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;
          }
    
    模板
    模板
    无效的
    矢量::
    _M_就位_后退_辅助(_参数&&…_参数)
    {
    常数大小\类型\长度=
    _M_check_len(尺寸类型(1),“向量::_M_emplace_back_aux”);
    //我们在这里进行分配
    指针uuu new_start(此->分配(uu len));
    指针\uuuu新建\u完成(\uuu新建\u开始);
    __试一试
    {
    //在这里,我们放置元素
    _Alloc_traits::construct(这个->_M_impl,_new_start+size(),
    标准::转发(_参数)…);
    __新完成=0;
    __新涂层
    =std::uu未初始化\u移动\u如果没有例外\u a
    (这个->这个开始,这个->这个结束,
    __新的开始,_M_get_Tp_分配器());
    ++__新涂层;
    }
    __捕获(…)
    {
    如果(!\u新的\u完成)
    _Alloc_traits::destroy(这个->_M_impl,uu new_start+size());
    其他的
    std::_Destroy(uuu new_start,uu new_finish,u M_get_Tp_allocator());
    _M_解除分配(u新开始,u len);
    __再次抛出异常;
    }
    std::_Destroy(这个->\u M\u impl.\u开始,这个->\u M\u impl.\u结束,
    _M_get_Tp_分配器());
    //在这里,我们摧毁了旧的记忆
    _M_解除分配(此->_M_impl._M_start,
    这是存储的->\u M\u impl.\u M\u end\u
    -这是一个简单的开始);
    这个->\u M\u impl.\u M\u start=\u新的\u start;
    这个->\u M\u impl.\u M\u finish=\u new\u finish;
    此->\u M\u impl.\u M\u end\u of\u storage=\u新的\u开始+\u len;
    }
    
    出于同样的原因,要求向后安置
    以确保安全;指针和引用的无效仅在修改方法调用返回时生效

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

  • 分配新容量
  • 在新数据段的末尾放置构造新元素
  • 将现有元素移动到新的数据段中
  • 销毁和取消分配旧数据段
  • At STL承认VC11未能支持
    v.emplace\u back(v[0])
    是一个bug,因此您应该明确检查您的库是否支持此用法,而不是想当然


    请注意,本标准明确禁止某些形式的自插入;例如,在[sequence.reqmts]第4段中,表100
    a.insert(p,i,j)
    有一个先决条件“
    i
    j
    不是
    a
    的迭代器。”

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

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

    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!
    
    std::vector v;
    v、 推回(0);
    //接下来将有更多的推回v,但此处未显示。。。
    v、 放回原处(v.back());//问题就在这里!
    
    上面的代码在Linux上使用g++STL运行,没有问题

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

    原因是,
    v.back()
    返回的引用无效,因为在最后添加元素之前,容器在
    v.emplace\u back()
    内达到其容量限制

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

    这似乎是一个问题(尚未解决)。 对于我尝试过的其他STL实现,问题没有出现


    最后,至少在您的代码使用Visual Studio编译并正常工作的情况下,现在应该避免将对向量元素的引用传递给同一向量的
    emplace_back()

    什么是“害怕因重新分配而使参数无效”@xylosper如果您处于向量容量的极限,push_back/emplace_back将重新分配向量以增加其大小。如果发生重新分配,对该向量的元素和迭代器的引用将无效。在这种情况下,push_back的参数在使用之前可能会失效。然而,事实证明std::vector跳过了环,所以这不是推回的问题。我认为理论上它是有保证的,但实际上你可能会遇到它,所以如果可能的话,你应该尽量避免它。@rasmus:我认为与案例相同的推理是:没有任何东西限制这个论点,因此,它很可能来自同一个向量。这个问题在
    emplace
    中变得更有趣:对向量本身的元素进行推回操作是完全安全的。例如,见。
    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!