为什么 ::operator new 已经足够了,还需要 ::operator new[]?

28

众所周知,C++标准定义了两种形式的全局分配函数:

void* operator new(size_t);
void* operator new[](size_t);

此外,C++草案标准(18.6.1.2 n3797)称:

 

227)operator new或operator delete直接不需要记录数组的重复次数或元素大小。这些操作在数组new和delete表达式中完成。但是,数组new表达式可以增加size参数以获取存储补充信息的空间。

我困惑的是:

如果我们从标准中删除void* operator new[](size_t);,只使用void* operator new(size_t),那该怎么办?为什么要定义冗余的全局分配函数?


有趣的问题。通常情况下,在将C++支持添加到仅支持C的系统中,operator newoperator new[]都会调用malloc函数。 - Mike DeSimone
4
这条注释很奇怪,因为非数组版本也可能通过增加operator new的size参数以获取存储补充信息的空间,尽管没有提到这一点。 - Keith
@Jefffrey:也许我误解了你的意思,但问题是关于这两个函数的区别。它们都只是接受要分配的字节数。似乎没有任何显著的区别。 - Cornstalks
2
@Keith:我记得在标准中读到过,这是明确禁止的——找到了——“只有当对象是数组时,它的大小才可以大于正在创建的对象的大小”。 - Tony Delroy
1
@Keith:你说“非数组版本也可能增加operator new的大小参数以获取存储补充信息的空间”,显然你是在谈论new[] 表达式,因为标准规定了增加的位置 - 我只是想说非数组等效物是不允许这样做的。如果你想推测它可能发生在其他地方,那很好但是不同。 - Tony Delroy
显示剩余2条评论
5个回答

13

我认为::operator new[]可能对于专门的系统很有用,其中“大但少”的数组可能会由不同于“小但众多”的对象的分配器分配。然而,它目前已经成为一种过时的东西。

operator new可以合理地期望在返回的确切地址处构造一个对象,但operator new[]则不能。分配块的前几个字节可能用于大小“cookie”,数组可能被稀疏初始化等等。这种区别对于成员operator new变得更加有意义,后者可能针对其特定类进行专门化。

无论如何,《code>::operator new[]都不可能非常重要,因为当前获取动态数组的最流行方法std::vector(通过std::allocator)忽略了它。

在现代C ++中,自定义分配器通常比自定义operator new更好的选择。实际上,应该避免使用new表达式,而是使用容器(或智能指针等)类,这些类提供更多的异常安全性。


我感到震惊、失望,甚至可以说是愤怒,因为std::vector没有使用new[]!哼。 - einpoklum
1
@einpoklum:vector需要放置new才能工作,而使用数组的放置new是一场噩梦,而尝试在像vector这样的预分配缓冲区中多次使用new[]是不可能以可移植的方式实现的。 - Mooing Duck
@david.pfx 这里哪部分是不正确的?你提到了“专门的数组索引”和“CDC、IBM等”,但这离具体的声明还有很长的路要走,而我的第一句话已经涵盖了这个范围。(我没有提到内存池,我说的是“由不同的分配器分配”,这包括了对架构特性的适应。)你回答中的“用于数组索引的额外存储空间”需要详细说明;需求可能比你想象的更严格。 - Potatoswatter
如果你想,你可以制作自己的分配器,它与 std::allocator 相同,除了调用 ::operator new[] 而不是 ::operator new(以及对应的 delete)。但是,这样做可能没有什么好处,因为每个实现已经针对默认的 std::vector 进行了优化,它使用非数组函数来分配数组。 - Potatoswatter

7
::operator new[]和~delete[]函数方便内存使用调试,成为审计分配和释放操作的核心点;您可以确保数组形式用于两个或都不用。
此外,还有许多合理但高度不寻常/粗糙的调整用途:
从单独的池中分配数组,也许是因为这极大地提高了小单对象动态分配对象的平均缓存命中率;
对于数组/非数组数据,具有不同的内存访问提示(例如madvise)。
所有这些都有点奇怪,超出99.999%程序员的日常关注范围,但为什么要阻止可能性呢?

@MooingDuck:仔细注意前导的“~”... O_o。 - Tony Delroy
我认为那是一个打字错误,除了伪析构函数名称,我从未遇到过那种语法。 - Mooing Duck
@MooingDuck:这是一种通用英语缩写,不是C++符号,常用于词典条目中表示前缀与之前的条目相同,例如:trouble <description> ~some <description of troublesome> ... - Tony Delroy
英语还是Perl?根据字典,我受到启发要 #define opərator operator - Potatoswatter
@Potatoswatter:尽情去吧……无论什么让你开心。 - Tony Delroy

6
标准(n3936)明确指出这两个运算符具有不同但相关的目的。
operator new调用函数void* operator new(std::size_t)。第一个参数必须与操作符的参数相同。它返回一个适当对齐的存储块,可能比所需的稍大一些。
operator new[]调用函数void* operator new[](std::size_t)。第一个参数可以比操作符提供的参数更大,以提供额外的存储空间,如果需要用于数组索引。两者的默认实现只是调用malloc()。
operator new[]的目的是支持专门的数组索引,如果可用的话。它与内存池或其他任何东西无关。在符合规范的实现中,使用此功能的实现将在额外的空间中设置专门的表,编译器将为指令或调用库支持例程生成代码,以利用这些表。在这些平台上,使用数组而未使用new[]的C++代码将失败。
我个人不知道任何这样的实现,但它类似于支持某些大型机(CDC、IBM等)的功能所需的特性,这些大型机的体系结构与我们所熟悉和喜爱的Intel或RISC芯片完全不同。
依我看,接受的答案是不正确的。
为了完整起见,标准(n3936主要在S5.3.4中)包含以下内容。
区分“数组对象”或“非数组对象”
引用“数组分配开销”,暗示可能需要额外的存储空间,它可能(以某种方式)用于重复计数或元素大小。
没有提到内存池或任何提示可能考虑这一点。

1
那么在这些实现中,std::vector<int, std::allocator<int>> 就不能工作了吗? - user541686
1
@Mehrdad:它们可以工作。这是一种_优化_,编译器可以执行以分配额外的空间,以便由(编译器生成的)专门查找代码使用。 - Daniel
2
那么我认为你误解了标准的那部分内容...许多现有的实现在返回指针的已知偏移处存储数组中元素数量的计数,以便delete[]可以找到该数字并调用适当数量的元素的析构函数 - 这解释了您提到的标准的所有部分。您的“专业数组索引”和“专业表格”听起来像是虚拟地址空间中数组的跨内存段碎片化,这是完全不同的前景,标准没有提及。 - Tony Delroy
@tonyd:我不同意,但实际上我几乎不理解你的观点,而且你在我的回答中读出了一些根本不存在的东西。如果你有什么要补充的(我认为你的观点是错误的),请随意添加,而不是试图在评论中进行辩论。 - david.pfx
1
@david.pfx:我不需要你的祝福来评论你的答案……你在一个公共论坛上,在这里请求澄清/参考,礼貌地建议另一种可能解释你所说的证据的方式,并友好地提到你的答案可能被误解是很常见的。这部分是为了那些想知道别人对你的答案持什么看法的人而提供的。你选择明确地在你的答案中驳斥我的回答(读者可能看不到),而不是进行评论——各有各的方式。 - Tony Delroy
显示剩余3条评论

2

我相信有适当的用例需要分别使用new[]new,但我还没有遇到过这种情况,这种分离是唯一可能的。

然而,我认为:由于用户调用不同版本的运算符new,如果他们只定义了一个operator new并将newnew[]都转发过去,C++标准将被指控故意且肆意地丢失信息。这里(字面上)只有一位信息,可能对某些人有用,我不认为委员会的成员可以在良心上扔掉它!

此外,对于我们其他人来说,需要实现额外的new[],甚至根本不是什么大不了的事情,因此保留单个比特的信息的权衡胜过在我们的程序中实现单个简单函数的小部分。


-1

C++编程语言:特别版第423页说:

operator new()operator delete()函数允许用户接管单个对象的分配和释放;operator new[]()operator delete[]()对于数组的分配和释放起到完全相同的作用。

感谢Tony D纠正了我的误解。

哇,我很少在C++上被某些我非常确定的事情抓住——我一定是花太多时间在Objective-C上了!

原始错误答案

很简单——new[]形式调用经典C数组中每个元素的构造函数。

因此,它首先为所有对象分配空间,然后迭代调用每个插槽的构造函数。


你在考虑一个“新表达式”,而不是使用“operator new”。微妙的不同。 - Mooing Duck
我对你的回答感到困惑。 我认为operator new用于定义使用new表达式时传递的效果。 你是说cplusplus.com上的链接也是错误的吗? - Andy Dent
2
@AndyDent:通常是这样的(我更喜欢cppreference或标准本身),但在这种情况下是正确的:“对数组类型使用new运算符的表达式首先调用函数operator new(即此函数)[...],如果成功,则自动初始化或构造数组中的每个对象(如果需要)。”请注意,它说的是new表达式负责调用运算符,然后执行您描述的构造... - Tony Delroy
根据Tony的理解,经过查阅圣经后,编辑了答案。 - Andy Dent

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