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;
}
出于同样的原因,要求向后安置
以确保安全;指针和引用的无效仅在修改方法调用返回时生效
实际上,这意味着执行重新分配需要按照以下顺序进行(忽略错误处理):
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!