分配器应该使用 construct() 进行默认初始化,而不是值初始化吗?

17
作为这个问题的后续,根据[default.allocator]的规定,必须将默认分配器(std::allocator<T>)的construct实现如下:
template <class U, class... Args>
void construct(U* p, Args&&... args);

Effects: ::new((void *)p) U(std::forward<Args>(args)...)

也就是说,始终进行值初始化。这样做的结果是,对于任何pod类型,std::vector<POD> v(num)将值初始化num个元素 - 这比默认初始化num个元素更昂贵。
为什么std::allocator没有提供默认初始化的附加重载呢?也就是说,类似于以下内容(从Casey借用):
template <class U>
void construct(U* p) noexcept(std::is_nothrow_default_constructible<U>::value)
{
    ::new(static_cast<void*>(p)) U;
}

在这些情况下,为什么偏好于值初始化?这似乎令人惊讶,因为它违反了通常的C++规则,即我们只为我们想要使用的付费。


我认为这样的改变是不可能的,因为目前std::vector<int> v(100)会给你100个0,但我想知道为什么会这样...既然同样可以要求std::vector<int> v2(100, 0),就像new int[100]new int[100]{}之间存在差异一样。


最近采纳了P0040 - FrankHB
@FrankHB 那篇论文是否会改变 vector 在这里的作用?它只是将算法添加到标准库中,对吗? - Barry
好的。所以这不是一个答案,只是一条评论。它提供了方便的接口来简化您的解决方法的实现。 - FrankHB
顺便说一句,与make_unique<T[]>类似的问题。 - dyp
1个回答

2
在C++03中,分配器(Allocators)的construct成员接受两个参数:指针和值,用于执行复制初始化:

20.1.6表格34

a.construct(p,t)

效果:
    ::new((void*)p) T(t)

construct接受两个参数可以追溯到1994年(第18页)。正如您所看到的,在原始的Stepanov概念中,它并不是分配器接口的一部分(它不应该是可配置的),而只是作为放置new的包装器存在。
唯一确定的方法是询问Stepanov本人,但我认为原因是:如果您想构造某些东西,则要使用特定值进行初始化。如果您希望未初始化整数,则可以省略construct调用,因为对于POD类型不需要该函数。后来,construct和其他相关函数被捆绑到分配器中,并将容器参数化,从而为最终用户引入了一些初始化控制的丧失。
因此,缺乏默认初始化似乎是出于历史原因:当 C++ 标准化时没有人考虑到它的重要性,而后来的标准版本也不会引入破坏性变化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接