【问题标题】:emplace_back() issue under VS2013VS2013 下的 emplace_back() 问题
【发布时间】:2016-04-07 17:40:40
【问题描述】:

考虑以下代码

std::vector<int> nums{21, 22, 23, 24};
nums.emplace_back(nums[0]);
nums.emplace_back(nums[1]);

for (auto n : nums) {
    std::cout << n << std::endl;
}

VS2013的输出

21
22
23
24
-17891602
22

为什么-17891602 在这里?

GCC 4.8.4的输出正确如下

21
22
23
24
21
22

然后我比较VS2013GCC之间emplace_back的实现

VS2013

template<class... _Valty>
    void emplace_back(_Valty&&... _Val)
    {   // insert by moving into element at end
    if (this->_Mylast == this->_Myend)
        _Reserve(1);
    _Orphan_range(this->_Mylast, this->_Mylast);
    this->_Getal().construct(this->_Mylast,
        _STD forward<_Valty>(_Val)...);
    ++this->_Mylast;
    }

海合会

template<typename _Tp, typename _Alloc>
template<typename... _Args>
  void
  vector<_Tp, _Alloc>::
  emplace_back(_Args&&... __args)
  {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
      {
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish;
      }
    else
      _M_emplace_back_aux(std::forward<_Args>(__args)...);
  }

VS2013 中似乎使用了奇怪的 _Reserve(1);。为什么?

编辑:

-17891602hex 值为0xFEEEFEEE,这意味着

被微软的 debug HeapFree() 用来标记释放的堆内存

参考magic number

然后我逐行调试上面的代码,发现调用了_Reserve(1);引起的0XFEEEFEEE

【问题讨论】:

标签: c++ c++11 gcc visual-studio-2013


【解决方案1】:

这是 VS2013 和 VS2015 在将元素放置到包含该元素的向量时的问题。如果向量调整大小,则对插入元素的引用无效。解决方法是在插入中创建元素的副本,然后将其插入。

auto n = nums[0];
nums.emplace_back(n);

_Reserve 调用是为了确保为向量分配了一些内存(因此不必在以后的操作中检查它)。

【讨论】:

  • 这是一个不幸的设计选择的解决方法。类似的解决方法是.emplace_back(nums[0]+0)。从逻辑上讲,添加 0 不应该改变结果!
  • @hvd 查看open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2164 中的讨论。目前的措辞似乎要求这样做。
  • @T.C.感谢您的参考。 nums.emplace_back(std::move(n)); 不需要工作;我错过了,因为emplace_back 采用了一个转发引用,它在这里变成了一个左值引用,所以使这个无效的规则不适用,即使在 [res.on.argument]1.3 的注释中特别提到了这一点。我已经删除了我之前的评论。显然很长一段时间以来,该计划是正式使其对左值引用也无效,根据那里的 #760 链接。
  • LWG 2164 大约是 emplace,而不是 emplace_back。这些实现之间的真正区别在于未显示的代码:在重新分配情况下会发生什么。如果新的对象构造抛出异常,则不能有任何影响。我知道这样做的唯一方法是首先在新缓冲区中构造新对象,然后将旧元素从旧缓冲区复制或移动到新缓冲区。如果 move 结构不是 noexcept,则必须使用 copy。要么 VS2013 想出了另一种方法来做到这一点,要么他们总是使用复制将旧元素移动到新缓冲区。
  • 或者他们没有提供强有力的异常安全保证。
【解决方案2】:

The emplace issue

绑定到emplace成员函数的函数参数包的对象不能是容器的元素或元素的子对象。

emplace_back()VS2013下的emplace()函数中被调用。

罢工>

  template<class... _Valty>
    iterator emplace(const_iterator _Where, _Valty&&... _Val)
    {   // insert by moving _Val at _Where
    size_type _Off = _VIPTR(_Where) - this->_Myfirst;

 #if _ITERATOR_DEBUG_LEVEL == 2
    if (size() < _Off)
        _DEBUG_ERROR("vector emplace iterator outside range");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

    emplace_back(_STD forward<_Valty>(_Val)...);
    _STD rotate(begin() + _Off, end() - 1, end());
    return (begin() + _Off);
    } 

我找到了一个很好的post,它描述了VS2013emplace_back()实现的一些细节。

std::vector 类有不同的实例成员(常规的和内部的),其中包括:

  • _Myfirst - 指向数据数组的开头
  • _Mylast - 指向数据数组中第一个未初始化的元素。如果等于_Myend,下一次插入将导致重新分配。你会在end() 电话上找到这个人
  • _Myend - 指向数据数组的末尾

因此,就内存地址而言,发生以下不等式:

_Myfirst &lt;=&lt;= _Mylast &lt;=&lt;= _Myend

看到里面有_Reserve(1) 的那一行了吗?这个函数调用会导致我们的 bug 暴露出来。

让我们逐步完成(参考前面的示例函数)。

nums.emplace_back(nums[0]);

首先我们得到一个对项目的引用,因为operator[] 返回一个reference

reference operator[](size_type _Pos) 
{ ... }

然后我们进入emplace_back 方法,将新鲜且有效的reference 传递给我们要插入的项目。我们在开始时立即看到的是对向量大小超出的检查。只要我们的插入导致向量增大其大小,我们就会在重新分配发生后立即使引用失效。这就是这种有趣但预期(一旦我们开始实施)行为的原因。

【讨论】:

  • 这是 LWG 显然不同意的提议决议。此外,这不会影响emplace_back
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多