为什么 C++ 中存在 delete[] 语法?

awi*_*ebe 125 c++ syntax standards memory-management language-lawyer

每次有人delete[]在这里问一个问题时,总会有一个非常笼统delete[]的回答:“ C++ 就是这样做的,使用”。来自普通的 C 背景,我不明白为什么需要一个不同的调用。

使用malloc()/free()您的选择是获取指向连续内存块的指针并释放连续内存块。实现领域中的某些东西会根据基地址知道您分配的块的大小,以便您何时必须释放它。

没有功能free_array()。我在与此相关的其他问题上看到了一些疯狂的理论,例如调用delete ptr只会释放数组的顶部,而不是整个数组。或者更正确的是,它不是由实现定义的。当然……如果这是 C++ 的第一个版本,并且您做出了一个有意义的奇怪设计选择。但是为什么 with$PRESENT_YEAR的 C++ 标准没有被重载???

似乎 C++ 添加的唯一额外的一点是遍历数组并调用析构函数,我认为这可能是它的症结所在,它实际上是使用一个单独的函数来为我们节省单个运行时长度查找,或者nullptr在列表的末尾,以换取折磨每个新的 C++ 程序员或程序员,他们有一个模糊的一天并且忘记了有一个不同的保留字。

如果除了“这就是标准所说的并且没有人质疑它”之外还有其他原因,有人可以一劳永逸地澄清吗?

Fra*_*eux 169

C++ 中的对象通常具有需要在其生命周期结束时运行的析构函数。delete[]确保调用数组每个元素的析构函数。但是这样做有未指定的开销,而delete没有。一种用于数组,它支付开销,另一种用于不支付开销的单个对象。

为了只有一个版本,实现需要一种机制来跟踪有关每个指针的额外信息。但是 C++ 的创始原则之一是用户不应该被迫支付他们绝对不必支付的成本。

永远delete是你new,永远delete[]是你new[]。但在现代C ++,newnew[]一般不再使用。使用std::make_uniquestd::make_sharedstd::vector或其他更富有表现力和更安全的替代品。

  • 哇,回复很快,感谢有关分配函数的提示。令人惊讶的是,C++ 中的答案经常是“不要使用该关键字”,请使用 std::someWeirdFunctionIntroducedInC++>=11() (61认同)
  • @awiebe 你是对的,大多数时候,如果有一个简洁的标准库功能来替换内置机制,它来自 C++11 或更高版本。C++11 基本上彻底改变了语言,允许以前无法实现的标准库功能。C++11 和之前版本之间的差异非常显着,基本上可以将它们视为两种不同的语言。在学习 C++ 时,请注意区分针对 C++03 及更早版本的教育材料和针对 C++11 及更高版本的材料。 (31认同)
  • @awiebe C++ 为您提供了尽可能接近硬件的工具。但这些工具通常功能强大且生硬,因此非常危险且难以有效使用。因此,它还通过标准库提供了一些工具,这些工具距离硬件“稍微”远一些,但非常安全和简单。这就是为什么您了解了如此多的功能,但被告知不要使用它们。因为除非你正在做一些非常独特或奇怪的事情,否则那些低级工具没有用。一般来说,比较方便的功能就可以了。 (25认同)
  • @awiebe,还请注意,像“new”这样的较低级别机制的存在允许大多数标准库(和其他库)用纯 C++ 编写(某些部分可能需要编译器支持)。因此,建议也可能是“仅使用这些来构建更高级别的抽象”。 (19认同)
  • @FrançoisAndrieux:用词挑剔“......这些工具通常强大而迟钝......”:我实际上将它们视为超级锋利的外科手术工具:你可以得到你想要的东西,你想要的方式。但缝合或清理外科手术需要同等的技能和材料,创可贴是做不到的。 (14认同)
  • 请注意,有时您必须将“new T()”与“delete[]”配对。如果实际类型是数组,“scalar-looking new”完全能够执行数组分配。 (4认同)
  • @Barmar我对其进行了重新措辞,以更清楚地表明“delete[]”可能具有与销毁每个数组元素相关的额​​外开销。 (3认同)
  • @Barmar我认为更多的是关于内存开销而不是速度。这个问题在新的放置中变得更加明显,由于这种潜在的开销,目前不可能对阵列进行可移植的新放置。请参阅[数组的新放置可以以便携式方式使用吗?](/sf/ask/1067811/) 。您可能需要传递比元素所需更多的存储空间,但不可能询问实现需要多少额外存储空间。 (3认同)
  • @dan04 标准要求创建“T”(“new T;”)的非数组新表达式需要从分配函数中请求准确的“sizeof(T)”字节。由于此规则,如果具有数组类型的 new 表达式在该平台上也没有数组分配开销,则只能将“new T;”实现为“new T[1];”。[参见](https://timsong-cpp.github.io/cppwp/expr.new#15) : *“该参数不得小于正在创建的对象的大小;它可能大于仅当对象是数组时才创建对象"*. (3认同)
  • 答案通常是“使用一些奇怪的、可能来自 C++11 的函数”,@awiebe,因为标准库的一个显着部分致力于提供围绕核心语言功能的包装器,让编译器为您完成繁重的工作每当您不需要将某些东西优化到绝对极限时。它以潜在的速度略有下降和/或可执行文件大小的增加来换取可读代码、减少您的簿记工作以及更好的安全保证。例如,“std::vector”本质上只是一个漂亮的包装器,它为您管理“new[]”和“delete[]”,还有额外的功能。 (2认同)

mil*_*bug 41

基本上,mallocfree分配内存,并newdelete创建和销毁对象。所以你必须知道对象是什么。

要详细说明 François Andrieux 的回答提到的未指定开销,您可以查看我对这个问题的回答,其中我检查了特定实现的作用(Visual C++ 2013,32位)。其他实现可能会也可能不会做类似的事情。

如果new[]与具有非平凡析构函数的对象数组一起使用,它所做的是多分配 4 个字节,并返回前移 4 个字节的指针,因此当delete[]想知道那里有多少对象时,它需要指针,先将其移动 4 个字节,然后获取该地址处的数字并将其视为存储在那里的对象数。然后它在每个对象上调用一个析构函数(从传递的指针的类型知道对象的大小)。然后,为了释放确切的地址,它传递了传递地址之前 4 个字节的地址。

在这个实现中,将分配的数组传递new[]给常规会delete导致调用第一个元素的单个析构函数,然后将错误的地址传递给释放函数,从而破坏堆。不要这样做!


dav*_*bak 34

东西不是在其他(都好)的答案中提到的是,这一现象的根本原因是阵列-由C继承-从来没有在C“一流”的事情++。

它们具有原始的 C 语义,没有 C++ 语义,因此没有 C++ 编译器和运行时支持,这将使您或编译器运行时系统使用指向它们的指针做有用的事情。

事实上,C++ 不支持它们,以至于指向一组事物的指针看起来就像指向单个事物的指针。特别是,如果数组是语言的适当部分,则不会发生这种情况——即使是作为库的一部分,如字符串或向量。

C++ 语言的这个问题是由于 C 的这种传统而发生的。它仍然是语言的一部分 - 即使我们现在有std::array固定长度的数组和(一直有)std::vector可变长度的数组 - 主要是为了兼容性:能够从 C++ 调用操作系统 API 和使用 C 语言互操作的其他语言编写的库。

而且……因为有大量书籍、网站和教室在他们的 C++ 教学法中很早就教授数组,因为 a) 能够在早期编写有用/有趣的示例,这些示例实际上调用了 OS API,当然因为 b) “这就是我们一直这样做的方式”的惊人力量。

  • 由于 new[] 的常见用途是在编译时分配一个大小未知的数组,因此 C 的数组指针无论如何并没有多大帮助。 (8认同)
  • 不过,指向数组的指针会立即衰减为指向元素的指针,这就是它的使用方式。,不是吗?有多少 C++(或 C)函数/方法签名采用数组指针类型?没有人,但没有人,教导这一点,也不是如何使用它。你不同意吗?例如,告诉我在 Unix/Linux API 中,在函数签名中,在文档假定为数组的裸指针上使用了指向数组的指针吗?@本沃伊特 (6认同)
  • _Effective C++ - 第三版_ (Meyers, 2008) 和 _More Effect C++_ (Meyers, 1996) 都没有提到数组指针类型。我可以继续从图书馆借书,但是……我真的不在乎。问题不在于在某个时候(甚至最初)这些语言在技术上是否具有这种能力。关键是还没有人用过它。曾经。我在回答中没有提到这一点并不意味着我不知道这一点。只是我知道这是编译器作者知识储备的无用遗迹。它从未被使用过,也从未被教导过。 (6认同)
  • 这里的核心问题是,数组指针和数组引用类型确实很难阅读,因此人们养成了不使用它们的习惯,这导致知识被抛在一边。使用它们的最简单方法是使用模板或“decltype”,并且使用它们通常很快就会变成[几乎不可读的混乱](https://www.ideone.com/nVDnRz)。这里的 create() 已经够糟糕的了(在很多方面),想象一下一个函数,它接受两个数组的指针并返回一个指向不同类型数组的指针。 (5认同)
  • 这个答案提出了许多完全错误的主张,显然是基于不知道 C 和 C++ 都支持“指向数组的指针”类型。这并不是缺乏表达数组指针的能力,而是在实践中没有使用这种能力。 (2认同)

plu*_*ash 6

通常,C++ 编译器及其关联的运行时构建在平台的 C 运行时之上。特别是在这种情况下 C 内存管理器。

C 内存管理器允许您在不知道内存大小的情况下释放内存块,但是没有标准方法可以从运行时获取块的大小,并且无法保证实际分配的块大小与您的大小完全相同。要求。它可能更大。

因此,由 C 内存管理器存储的块大小不能有效地用于启用更高级别的功能。如果更高级别的功能需要有关分配大小的信息,那么它必须自己存储它。(delete[]对于带有析构函数的类型,C++确实需要它,以便为每个元素运行它们。)

C++ 也有一种“你只为你使用的东西付费”的态度,为每个分配存储一个额外的长度字段(与底层分配器的簿记分开)不适合这种态度。

由于在 C 和 C++ 中表示未知(在编译时)大小的数组的正常方法是使用指向其第一个元素的指针,因此编译器无法根据类型区分单个对象分配和数组分配系统。所以它留给程序员来区分。