我知道当delete []
导致所有数组元素被破坏然后释放内存时。
我最初认为编译器只是想为数组中的所有元素调用析构函数,但我还有一个反对的参数:
堆内存分配器必须知道分配的字节大小,并且使用sizeof(Type)
可以找不到元素,并为数组调用适当的no析构函数以防止资源泄漏。
所以我的假设是正确的,请清楚我对此的怀疑。
所以我没有在[]
中使用delete []
?
答案 0 :(得分:35)
Scott Meyers在他的Effective C ++书中说:第5项:在相应的new和delete使用中使用相同的表单。
删除的一个重要问题是:被删除的内存中有多少个对象?答案决定了必须调用多少个析构函数。
删除的指针是指向单个对象还是指向对象数组?删除知道的唯一方法是让你告诉它。如果在使用delete时不使用括号,则delete假定指向了单个对象。
此外,内存分配器可能会分配更多存储对象所需的空间,在这种情况下,将每个对象大小返回的内存块大小分开将不起作用。
根据平台,_msize
(窗口),malloc_usable_size
(linux)或malloc_size
(osx)函数将告诉您已分配的块的实际长度。在设计种植容器时可以利用这些信息。
它不起作用的另一个原因是Foo* foo = new Foo[10]
调用operator new[]
来分配内存。然后delete [] foo;
调用operator delete[]
来释放内存。由于这些运算符可能会超载,您必须遵守约定,否则delete foo;
调用operator delete
,这可能与operator delete []
实现不兼容。这是一个问题语义,而不仅仅是跟踪已分配对象的数量,以便稍后发出正确数量的析构函数调用。
另见:
简短回答:魔术。
长答案:运行时系统存储对象的数量n,如果你只知道指针p,它可以在哪里检索它。有两种流行的技术可以做到这一点。这两种技术都被商业级编译器使用,两者都有权衡,而且都不是完美的。这些技巧是:
我们如何确保正确删除数组?特别是,我们如何确保为数组的所有元素调用析构函数?
...
不需要普通删除来处理单个对象和数组。这避免了分配和解除分配单个对象的常见情况的复杂化。它还避免使用数组释放所需的信息来阻止单个对象。
delete []的中间版本要求程序员指定数组的元素数。
...
事实证明这很容易出错,因此跟踪元素数量的负担就转移到了实现上。
正如@Marcus所说,理性可能是“你不为你不使用的东西买单”。
EDIT2:
在“The C ++ Programming Language,3rd edition”,§10.4.7中,Bjarne Stroustrup写道:
究竟如何分配数组和单个对象取决于实现。因此,不同的实现将对delete和delete []运算符的错误使用做出不同的反应。在前一个简单且无趣的情况下,编译器可以检测到问题,但通常会在运行时发生令人讨厌的事情。
数组的特殊销毁运算符delete []在逻辑上不是必需的。但是,假设已经要求免费存储的实现为每个对象保存足够的信息,以告知它是个人还是数组。用户本可以免除负担,但这种义务会给某些C ++实现带来大量的时间和空间开销。
答案 1 :(得分:10)
决定保持单独的delete
和delete[]
的主要原因是这两个实体并不像第一眼看上去那么相似。对于一个天真的观察者来说,他们似乎几乎是相同的:只是破坏和解除分配,只有潜在的待处理对象数量的差异。实际上,差异更为显着。
两者之间最重要的区别是delete
可能执行对象的多态删除,即所讨论的对象的静态类型可能与其动态类型不同。另一方面,delete[]
必须严格处理数组的非多态删除。因此,在这两个实体内部实现了两者之间明显不同且不相交的逻辑。由于多态删除的可能性,delete
的功能甚至与1个元素的数组上的delete[]
的功能远远不同,因为一个天真的观察者可能最初错误地假设。
与其他一些答案中提出的奇怪主张相反,当然,完全有可能只用一个在早期阶段就会分支的构造来替换delete
和delete[]
,即它将使用由new
/ new[]
存储的家庭信息确定内存块的类型(数组与否),然后跳转到相应的{{1}的相应功能}或delete
。然而,这将是一个相当糟糕的设计决策,因为再一次,两者的功能太不同了。将两者强制为单个构造将类似于创建具有释放功能的瑞士军刀。此外,为了能够从非数组中分辨出一个数组,我们必须将一个额外的家庭信息引入到使用普通delete[]
完成的单对象内存分配中。这可能很容易导致单个对象分配中显着的内存开销。
但是,这里的主要原因是new
和delete
之间的功能差异。这些语言实体仅具有明显的皮肤深度相似性,仅存在于天真规范(“破坏和自由记忆”)的层面,但是一旦人们逐渐了解这些实体真正需要做什么就会意识到它们太过不同了。合并成一个。
P.S。这是BTW关于您在问题中提出delete[]
的建议的问题之一。由于sizeof(type)
具有潜在的多态性,您不知道delete
中的类型,这就是您无法获得任何delete
的原因。这个想法存在更多问题,但已经足以解释为什么它不会飞。
答案 2 :(得分:3)
堆本身知道已分配块的大小 - 您只需要该地址。看起来像free()有效 - 你只传递地址并释放内存。
delete
(delete[]
)和free()
之间的区别在于前两者首先调用析构函数,然后释放内存(可能使用free()
)。问题是delete[]
也只有一个参数 - 地址,只有那个地址需要知道运行析构函数的对象数。所以new[]
使用som实现定义的方法来编写元素的数量 - 通常它在数组前面加上元素的数量。现在delete []将依赖于特定于实现的数据来运行析构函数然后释放内存(再次,仅使用块地址)。
答案 3 :(得分:1)
delete[]
只调用不同的实现(函数);
没有理由分配器无法跟踪它(实际上,编写自己的代码很容易)。
如果我猜的话,我不知道他们没有管理它的原因,或实施的历史:其中很多'好吧,为什么这不是更简单?'问题(在C ++中)归结为以下一个或多个:
在这种情况下,表现。使用delete
vs delete[]
很容易,我相信它可以从程序员中抽象出来并且速度相当快(一般用途)。 delete[]
只需要一些额外的函数调用和操作(省略析构函数调用),但这是每次调用删除,而且不必要,因为程序员通常知道他/她正在处理的类型(如果没有,可能会有更大的问题)。所以它只是避免通过分配器调用。另外,分配器可能不需要详细跟踪这些单个分配;将每个分配作为一个数组进行处理将需要额外的条目来计算普通分配,因此它是多级简单分配器实现简化,这对于许多人来说实际上很重要,因为它是一个非常低级别的域。
答案 4 :(得分:-1)
这更复杂。
使用它来删除数组的关键字和约定是为了方便实现而发明的,有些实现确实使用它(我不知道哪个。但是MS VC ++没有)。
方便之处在于:
在所有其他情况下,您知道通过其他方式释放的确切大小。删除单个对象时,可以使用编译时sizeof()的大小。当您通过基指针删除多态对象并且您有一个虚拟析构函数时,您可以将该大小作为vtbl中的单独条目。如果删除数组,除非单独跟踪,否则如何知道要释放的内存大小?
特殊语法允许仅为数组跟踪此类大小 - 例如,将其放在返回给用户的地址之前。这会占用额外的资源,非阵列也不需要。