可以std::vector emplace_back从vector本身的元素复制构造函数
Can std::vector emplace_back copy construct from an element of the vector itself?
当使用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.
然而,当使用emplace_back
时,std::vector
将实参转发给std::string
的构造函数,以便在vector中进行复制构造。这使我怀疑vector的重新分配发生在新字符串复制构造之前(否则它不会被分配到位),从而在使用前使实参无效。
这是否意味着我不能用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?
emplace_back
必须是安全的,原因与push_back
必须是安全的相同;指针和引用的无效只在修改方法调用返回时生效。
emplace_back
执行重新分配需要按照以下顺序进行(忽略错误处理):
- 分配新容量
- 在新数据段 的末尾放置-构造新元素
- 移动-构建现有元素到新的数据段
- 销毁旧数据段并释放
在这个reddit线程STL承认VC11支持v.emplace_back(v[0])
的失败是一个bug,所以你应该明确检查你的库是否支持这种用法,而不是想当然。
注意某些形式的自插入是标准特别禁止的;例如[sequence]。表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!
上面的代码在Linux上用g++ STL运行没有问题。
在Windows上运行相同的代码时(使用Visual Studio 2013 Update5编译),向量有时包含一些乱码元素(看似随机值)。
原因是在最后添加元素之前,v.back()
返回的引用由于v.emplace_back()
内的容器达到其容量限制而无效。
我研究了vc++的emplace_back()
的STL实现,它似乎分配了新的存储空间,将现有的向量元素复制到新的存储位置,释放旧的存储空间,然后在新存储空间的末尾构造元素。此时,被引用元素的底层内存可能已经被释放或无效。这会产生未定义的行为,导致在重新分配阈值处插入的向量元素被乱码。
这似乎是Visual Studio中的一个(仍未修复)bug。对于我尝试的其他STL实现,没有发生问题。
最后,你现在应该避免将对vector元素的引用传递给同一个vector的emplace_back()
,至少如果你的代码在Visual Studio中编译并且应该工作的话。
我检查了我的矢量实现,它在这里工作如下:
- 分配新内存 <
- 安置对象/gh> 释放旧内存
所以这里一切都很好。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;
}
- 对 Vector 元素 (C++) 的push_back操作
- 有没有函数可以直接获取 std::vector 元素的大小?
- 使用 stl 排列 std::vector 元素的最短解决方案
- 在不调用构造函数的情况下创建 Vector 元素
- 分配std :: vector元素的两种方法
- 如何对std::vector元素中的对象使用shared_mutex
- 在 GDB 漂亮的打印机中显示特定的 std::vector 元素
- 将分配到std :: vector元素线程安全
- 是否可以使用 std::vector 元素的地址作为指针
- 访问std::vector元素的速度会随着向量大小而减慢
- 指向 std::vector 元素时编译器错误<bool>?
- 通过 std::vector.data() 指向 std::vector 元素的指针指向损坏的数据
- 指向所有 std::vector 元素的指针
- 调用在函数调用期间重新分配的“std::vector”元素的函数是否会导致麻烦
- 使用下标运算符进行Integer Vector元素比较失败,但使用at函数成功
- 填充一个无序的vector元素指针队列
- 将vector元素赋值给调用emplace_back的函数的result
- 调用带有引用vector元素参数的c++函数
- 将vector元素转换为字符串进行比较
- vector元素被覆盖