将std::uninitialized_fill()与任何分配器一起使用有意义吗
Does it make sense to use std::uninitialized_fill() with any allocator?
当用户作为参数传递的分配器用于获取内存本身时,用库中的std::uninitialized_fill()
初始化内存有意义吗?我之所以这么问,是因为分配器应该提供自己的construct()
方法(而不是allocate()
方法),其实现可能与标准方法不同,所以std::uninitialized_fill()
可能并不总是适用于所有情况。
确切地说,我的疑虑来自Stroustrup写的C++书(附录E"标准库异常安全",E.3.1节),作者在书中给出了template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a)
的可能实现:分配器a用于获得向量的内存,然后std::uninitialized_fill()
用于初始化获得的内存。
他还给出了std::uninitialized_fill()
的实现,它在内部使用标准放置new来初始化内存,但不再有证据表明分配器的construct()
方法作为参数传递给向量构造函数。
我检查了vector的GCC实现,它显示:
vector(size_type __n, const value_type& __value,
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_fill_initialize(__n, __value); }
...
_M_fill_initialize(size_type __n, const value_type& __value)
{
this->_M_impl._M_finish =
std::__uninitialized_fill_n_a(this->_M_impl._M_start, __n, __value,
_M_get_Tp_allocator());
}
因此,看起来分配器的信息并没有消失。然而,uninitialized_fill_n_a
的代码很奇怪,它有两个重载(见下文),一个用于通用分配器,另一个用于std::allocator
。
construct
的通用调用和std::allocator
的专用调用只是调用std::uninitialized_fill()
。
因此,我的结论如下,
1) 所有std::allocator
的construct
都具有与放置new和相同的效果
2) 这对于通用分配器来说是不能假设的,或者至少GCC std::vector
没有假设。(根据@Michael Burr的说法,你可以在C++03中假设这一点,不幸的是,他没有提到C++11)。
因此使用构造(即std::allocator_traits<Alloc>::construct(alloc, pointer, value)
)
我个人的看法(经过调查)是,
i) std::uninitialized_fill
有缺陷,因为它应该使用分配器的最后一个可选参数(或另一个重载)
ii)作为一种变通方法,在您的实现细节中,您应该有一个.uninitialized_fill(first, last, value, alloc)
函数来完成这项工作,包括处理异常(请注意以下如何使用故障时销毁来展开构造)。
iii)当你有关于分配器的信息时,当前的std::unitialized_fill
是非常无用的(基本上你必须重新实现它。)
现在上面引用的代码:
template<typename _ForwardIterator, typename _Size, typename _Tp,
typename _Allocator>
_ForwardIterator
__uninitialized_fill_n_a(_ForwardIterator __first, _Size __n,
const _Tp& __x, _Allocator& __alloc)
{
_ForwardIterator __cur = __first;
__try
{
typedef __gnu_cxx::__alloc_traits<_Allocator> __traits;
for (; __n > 0; --__n, ++__cur)
__traits::construct(__alloc, std::__addressof(*__cur), __x);
return __cur;
}
__catch(...)
{
std::_Destroy(__first, __cur, __alloc);
__throw_exception_again;
}
}
template<typename _ForwardIterator, typename _Size, typename _Tp,
typename _Tp2>
inline _ForwardIterator
__uninitialized_fill_n_a(_ForwardIterator __first, _Size __n,
const _Tp& __x, allocator<_Tp2>&)
{ return std::uninitialized_fill_n(__first, __n, __x); }
我宁愿调用construct
。
提供自定义分配器有多种原因,更好的分配策略(使用池/堆栈存储)并不是唯一的原因。
我还看到(并涉足)调试分配器,以帮助跟踪自定义容器中不正确的分配器使用情况。还可以使用分配器来跟踪内存使用情况、收集统计信息等
因此,construct
可能(或不)具有超出对象构造的附加效果,因此不一定等同于简单的放置new
。
在我的调试分配器中,在没有调用该地址上的construct
的情况下调用destroy
是一个错误(同样在没有调用destroy
的情况下回收内存),所以至少我建议保持一致,将construct
与destroy
配对,并将new
放置在显式析构函数调用中。
任何非默认分配器的construct()
函数的实现都需要具有放置新的效果(根据C++03中的表32-分配器要求)。
因此,即使使用了自定义分配器,使用std::uninitialized_fill()
(定义为对范围中的每个元素执行新的放置)也应该很好。
- 如何将enable-if与模板参数和参数包一起使用
- 如何将PERF_AMPLE_READ与mmap一起使用
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 如何将C++中的库和头与MinGW一起使用
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 为什么我不能将 rand() 与数组的大小一起使用?
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 将fold表达式与std::一起用于两个元组
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 将 std::allocate_shared 与多态资源分配器一起使用
- 如何将分配器与 std::set 一起使用?
- 如何将重新绑定与自定义分配器和自定义列表一起使用
- shared_ptr自定义分配器与自定义删除器一起
- 将运算符new和运算符delete与自定义内存池/分配器一起使用
- 自定义分配器有时会与stl向量一起崩溃
- 与自定义分配器一起使用的最佳唯一指针是什么
- 将 std::unique_ptr 与自定义分配器一起使用,以包装原始指针
- 将std::uninitialized_fill()与任何分配器一起使用有意义吗
- 将std::uniqueptr与分配器一起使用