当使用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?
答案 0 :(得分:19)
emplace_back
必须是安全的push_back
is required to be safe;一旦修改方法调用返回,指针和引用的失效才有效。
实际上,这意味着执行重新分配的emplace_back
需要按以下顺序进行(忽略错误处理):
在this reddit thread STL确认VC11无法支持v.emplace_back(v[0])
作为错误,因此您一定要检查您的库是否支持此用法,而不是理所当然。
请注意,标准明确禁止某些形式的自我插入 ;例如在 [sequence.reqmts] 第4段表100 a.insert(p,i,j)
具有先决条件&#34; i
和j
不是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)
我检查了我的矢量实现,它的工作原理如下:
所以一切都很好。类似的实现用于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;
}